|
1 /** |
|
2 * Copyright (c) 2012, Ben Fortuna |
|
3 * All rights reserved. |
|
4 * |
|
5 * Redistribution and use in source and binary forms, with or without |
|
6 * modification, are permitted provided that the following conditions |
|
7 * are met: |
|
8 * |
|
9 * o Redistributions of source code must retain the above copyright |
|
10 * notice, this list of conditions and the following disclaimer. |
|
11 * |
|
12 * o Redistributions in binary form must reproduce the above copyright |
|
13 * notice, this list of conditions and the following disclaimer in the |
|
14 * documentation and/or other materials provided with the distribution. |
|
15 * |
|
16 * o Neither the name of Ben Fortuna nor the names of any other contributors |
|
17 * may be used to endorse or promote products derived from this software |
|
18 * without specific prior written permission. |
|
19 * |
|
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
|
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
|
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
|
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
|
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
31 */ |
|
32 package net.fortuna.ical4j.model.component; |
|
33 |
|
34 import java.io.IOException; |
|
35 import java.net.URISyntaxException; |
|
36 import java.text.ParseException; |
|
37 import java.util.Iterator; |
|
38 |
|
39 import net.fortuna.ical4j.model.Component; |
|
40 import net.fortuna.ical4j.model.ComponentList; |
|
41 import net.fortuna.ical4j.model.Date; |
|
42 import net.fortuna.ical4j.model.Property; |
|
43 import net.fortuna.ical4j.model.PropertyList; |
|
44 import net.fortuna.ical4j.model.ValidationException; |
|
45 import net.fortuna.ical4j.model.Validator; |
|
46 import net.fortuna.ical4j.model.property.LastModified; |
|
47 import net.fortuna.ical4j.model.property.Method; |
|
48 import net.fortuna.ical4j.model.property.TzId; |
|
49 import net.fortuna.ical4j.model.property.TzUrl; |
|
50 import net.fortuna.ical4j.util.PropertyValidator; |
|
51 import net.fortuna.ical4j.util.Strings; |
|
52 |
|
53 import org.apache.commons.lang.ObjectUtils; |
|
54 import org.apache.commons.lang.builder.HashCodeBuilder; |
|
55 |
|
56 /** |
|
57 * $Id$ [Apr 5, 2004] |
|
58 * |
|
59 * Defines an iCalendar VTIMEZONE component. |
|
60 * |
|
61 * <pre> |
|
62 * 4.6.5 Time Zone Component |
|
63 * |
|
64 * Component Name: VTIMEZONE |
|
65 * |
|
66 * Purpose: Provide a grouping of component properties that defines a |
|
67 * time zone. |
|
68 * |
|
69 * Formal Definition: A "VTIMEZONE" calendar component is defined by the |
|
70 * following notation: |
|
71 * |
|
72 * timezonec = "BEGIN" ":" "VTIMEZONE" CRLF |
|
73 * |
|
74 * 2*( |
|
75 * |
|
76 * ; 'tzid' is required, but MUST NOT occur more |
|
77 * ; than once |
|
78 * |
|
79 * tzid / |
|
80 * |
|
81 * ; 'last-mod' and 'tzurl' are optional, |
|
82 * but MUST NOT occur more than once |
|
83 * |
|
84 * last-mod / tzurl / |
|
85 * |
|
86 * ; one of 'standardc' or 'daylightc' MUST occur |
|
87 * ..; and each MAY occur more than once. |
|
88 * |
|
89 * standardc / daylightc / |
|
90 * |
|
91 * ; the following is optional, |
|
92 * ; and MAY occur more than once |
|
93 * |
|
94 * x-prop |
|
95 * |
|
96 * ) |
|
97 * |
|
98 * "END" ":" "VTIMEZONE" CRLF |
|
99 * |
|
100 * standardc = "BEGIN" ":" "STANDARD" CRLF |
|
101 * |
|
102 * tzprop |
|
103 * |
|
104 * "END" ":" "STANDARD" CRLF |
|
105 * |
|
106 * daylightc = "BEGIN" ":" "DAYLIGHT" CRLF |
|
107 * |
|
108 * tzprop |
|
109 * |
|
110 * "END" ":" "DAYLIGHT" CRLF |
|
111 * |
|
112 * tzprop = 3*( |
|
113 * |
|
114 * ; the following are each REQUIRED, |
|
115 * ; but MUST NOT occur more than once |
|
116 * |
|
117 * dtstart / tzoffsetto / tzoffsetfrom / |
|
118 * |
|
119 * ; the following are optional, |
|
120 * ; and MAY occur more than once |
|
121 * |
|
122 * comment / rdate / rrule / tzname / x-prop |
|
123 * |
|
124 * ) |
|
125 * </pre> |
|
126 * |
|
127 * @author Ben Fortuna |
|
128 */ |
|
129 public class VTimeZone extends CalendarComponent { |
|
130 |
|
131 private static final long serialVersionUID = 5629679741050917815L; |
|
132 |
|
133 private final Validator itipValidator = new ITIPValidator(); |
|
134 |
|
135 private ComponentList observances; |
|
136 |
|
137 /** |
|
138 * Default constructor. |
|
139 */ |
|
140 public VTimeZone() { |
|
141 super(VTIMEZONE); |
|
142 this.observances = new ComponentList(); |
|
143 } |
|
144 |
|
145 /** |
|
146 * Constructs a new instance containing the specified properties. |
|
147 * @param properties a list of properties |
|
148 */ |
|
149 public VTimeZone(final PropertyList properties) { |
|
150 super(VTIMEZONE, properties); |
|
151 this.observances = new ComponentList(); |
|
152 } |
|
153 |
|
154 /** |
|
155 * Constructs a new vtimezone component with no properties and the specified list of type components. |
|
156 * @param observances a list of type components |
|
157 */ |
|
158 public VTimeZone(final ComponentList observances) { |
|
159 super(VTIMEZONE); |
|
160 this.observances = observances; |
|
161 } |
|
162 |
|
163 /** |
|
164 * Constructor. |
|
165 * @param properties a list of properties |
|
166 * @param observances a list of timezone types |
|
167 */ |
|
168 public VTimeZone(final PropertyList properties, |
|
169 final ComponentList observances) { |
|
170 super(VTIMEZONE, properties); |
|
171 this.observances = observances; |
|
172 } |
|
173 |
|
174 /** |
|
175 * {@inheritDoc} |
|
176 */ |
|
177 public final String toString() { |
|
178 final StringBuffer b = new StringBuffer(); |
|
179 b.append(BEGIN); |
|
180 b.append(':'); |
|
181 b.append(getName()); |
|
182 b.append(Strings.LINE_SEPARATOR); |
|
183 b.append(getProperties()); |
|
184 b.append(observances); |
|
185 b.append(END); |
|
186 b.append(':'); |
|
187 b.append(getName()); |
|
188 b.append(Strings.LINE_SEPARATOR); |
|
189 return b.toString(); |
|
190 } |
|
191 |
|
192 /** |
|
193 * {@inheritDoc} |
|
194 */ |
|
195 public final void validate(final boolean recurse) |
|
196 throws ValidationException { |
|
197 |
|
198 /* |
|
199 * ; 'tzid' is required, but MUST NOT occur more ; than once tzid / |
|
200 */ |
|
201 PropertyValidator.getInstance().assertOne(Property.TZID, |
|
202 getProperties()); |
|
203 |
|
204 /* |
|
205 * ; 'last-mod' and 'tzurl' are optional, but MUST NOT occur more than once last-mod / tzurl / |
|
206 */ |
|
207 PropertyValidator.getInstance().assertOneOrLess(Property.LAST_MODIFIED, |
|
208 getProperties()); |
|
209 PropertyValidator.getInstance().assertOneOrLess(Property.TZURL, |
|
210 getProperties()); |
|
211 |
|
212 /* |
|
213 * ; one of 'standardc' or 'daylightc' MUST occur ..; and each MAY occur more than once. standardc / daylightc / |
|
214 */ |
|
215 if (getObservances().getComponent(Observance.STANDARD) == null |
|
216 && getObservances().getComponent(Observance.DAYLIGHT) == null) { |
|
217 throw new ValidationException("Sub-components [" |
|
218 + Observance.STANDARD + "," + Observance.DAYLIGHT |
|
219 + "] must be specified at least once"); |
|
220 } |
|
221 |
|
222 for (final Iterator i = getObservances().iterator(); i.hasNext();) { |
|
223 ((Component) i.next()).validate(recurse); |
|
224 } |
|
225 |
|
226 /* |
|
227 * ; the following is optional, ; and MAY occur more than once x-prop |
|
228 */ |
|
229 |
|
230 if (recurse) { |
|
231 validateProperties(); |
|
232 } |
|
233 } |
|
234 |
|
235 /** |
|
236 * {@inheritDoc} |
|
237 */ |
|
238 protected Validator getValidator(Method method) { |
|
239 return itipValidator; |
|
240 } |
|
241 |
|
242 /** |
|
243 * Common validation for all iTIP methods. |
|
244 * |
|
245 * <pre> |
|
246 * Component/Property Presence |
|
247 * ------------------- ---------------------------------------------- |
|
248 * VTIMEZONE 0+ MUST be present if any date/time refers |
|
249 * to timezone |
|
250 * DAYLIGHT 0+ MUST be one or more of either STANDARD or |
|
251 * DAYLIGHT |
|
252 * COMMENT 0 or 1 |
|
253 * DTSTART 1 MUST be local time format |
|
254 * RDATE 0+ if present RRULE MUST NOT be present |
|
255 * RRULE 0+ if present RDATE MUST NOT be present |
|
256 * TZNAME 0 or 1 |
|
257 * TZOFFSET 1 |
|
258 * TZOFFSETFROM 1 |
|
259 * TZOFFSETTO 1 |
|
260 * X-PROPERTY 0+ |
|
261 * LAST-MODIFIED 0 or 1 |
|
262 * STANDARD 0+ MUST be one or more of either STANDARD or |
|
263 * DAYLIGHT |
|
264 * COMMENT 0 or 1 |
|
265 * DTSTART 1 MUST be local time format |
|
266 * RDATE 0+ if present RRULE MUST NOT be present |
|
267 * RRULE 0+ if present RDATE MUST NOT be present |
|
268 * TZNAME 0 or 1 |
|
269 * TZOFFSETFROM 1 |
|
270 * TZOFFSETTO 1 |
|
271 * X-PROPERTY 0+ |
|
272 * TZID 1 |
|
273 * TZURL 0 or 1 |
|
274 * X-PROPERTY 0+ |
|
275 * </pre> |
|
276 */ |
|
277 private class ITIPValidator implements Validator { |
|
278 |
|
279 private static final long serialVersionUID = 1L; |
|
280 |
|
281 /** |
|
282 * {@inheritDoc} |
|
283 */ |
|
284 public void validate() throws ValidationException { |
|
285 for (final Iterator i = getObservances().iterator(); i.hasNext();) { |
|
286 final Observance observance = (Observance) i.next(); |
|
287 PropertyValidator.getInstance().assertOne(Property.DTSTART, observance.getProperties()); |
|
288 PropertyValidator.getInstance().assertOne(Property.TZOFFSETFROM, observance.getProperties()); |
|
289 PropertyValidator.getInstance().assertOne(Property.TZOFFSETTO, observance.getProperties()); |
|
290 |
|
291 PropertyValidator.getInstance().assertOneOrLess(Property.COMMENT, observance.getProperties()); |
|
292 PropertyValidator.getInstance().assertOneOrLess(Property.TZNAME, observance.getProperties()); |
|
293 } |
|
294 } |
|
295 } |
|
296 |
|
297 /** |
|
298 * @return Returns the types. |
|
299 */ |
|
300 public final ComponentList getObservances() { |
|
301 return observances; |
|
302 } |
|
303 |
|
304 /** |
|
305 * Returns the latest applicable timezone observance for the specified date. |
|
306 * @param date the latest possible date for a timezone observance onset |
|
307 * @return the latest applicable timezone observance for the specified date or null if there are no applicable |
|
308 * observances |
|
309 */ |
|
310 public final Observance getApplicableObservance(final Date date) { |
|
311 Observance latestObservance = null; |
|
312 Date latestOnset = null; |
|
313 for (final Iterator i = getObservances().iterator(); i.hasNext();) { |
|
314 final Observance observance = (Observance) i.next(); |
|
315 final Date onset = observance.getLatestOnset(date); |
|
316 if (latestOnset == null |
|
317 || (onset != null && onset.after(latestOnset))) { |
|
318 latestOnset = onset; |
|
319 latestObservance = observance; |
|
320 } |
|
321 } |
|
322 return latestObservance; |
|
323 } |
|
324 |
|
325 /** |
|
326 * @return the mandatory timezone identifier property |
|
327 */ |
|
328 public final TzId getTimeZoneId() { |
|
329 return (TzId) getProperty(Property.TZID); |
|
330 } |
|
331 |
|
332 /** |
|
333 * @return the optional last-modified property |
|
334 */ |
|
335 public final LastModified getLastModified() { |
|
336 return (LastModified) getProperty(Property.LAST_MODIFIED); |
|
337 } |
|
338 |
|
339 /** |
|
340 * @return the optional timezone url property |
|
341 */ |
|
342 public final TzUrl getTimeZoneUrl() { |
|
343 return (TzUrl) getProperty(Property.TZURL); |
|
344 } |
|
345 |
|
346 /** |
|
347 * {@inheritDoc} |
|
348 */ |
|
349 public boolean equals(final Object arg0) { |
|
350 if (arg0 instanceof VTimeZone) { |
|
351 return super.equals(arg0) |
|
352 && ObjectUtils.equals(observances, ((VTimeZone) arg0) |
|
353 .getObservances()); |
|
354 } |
|
355 return super.equals(arg0); |
|
356 } |
|
357 |
|
358 /** |
|
359 * {@inheritDoc} |
|
360 */ |
|
361 public int hashCode() { |
|
362 return new HashCodeBuilder().append(getName()).append(getProperties()) |
|
363 .append(getObservances()).toHashCode(); |
|
364 } |
|
365 |
|
366 /** |
|
367 * Overrides default copy method to add support for copying observance sub-components. |
|
368 * @return a copy of the instance |
|
369 * @throws ParseException where an error occurs parsing data |
|
370 * @throws IOException where an error occurs reading data |
|
371 * @throws URISyntaxException where an invalid URI is encountered |
|
372 * @see net.fortuna.ical4j.model.Component#copy() |
|
373 */ |
|
374 public Component copy() throws ParseException, IOException, URISyntaxException { |
|
375 final VTimeZone copy = (VTimeZone) super.copy(); |
|
376 copy.observances = new ComponentList(observances); |
|
377 return copy; |
|
378 } |
|
379 } |