src/net/fortuna/ical4j/data/CalendarBuilder.java

changeset 0
fb9019fb1bf7
equal deleted inserted replaced
-1:000000000000 0:77676761e75d
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.data;
33
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.InputStreamReader;
37 import java.io.Reader;
38 import java.net.URISyntaxException;
39 import java.nio.charset.Charset;
40 import java.text.ParseException;
41 import java.util.ArrayList;
42 import java.util.Iterator;
43 import java.util.List;
44
45 import net.fortuna.ical4j.model.Calendar;
46 import net.fortuna.ical4j.model.CalendarException;
47 import net.fortuna.ical4j.model.Component;
48 import net.fortuna.ical4j.model.ComponentFactory;
49 import net.fortuna.ical4j.model.Escapable;
50 import net.fortuna.ical4j.model.Parameter;
51 import net.fortuna.ical4j.model.ParameterFactory;
52 import net.fortuna.ical4j.model.ParameterFactoryRegistry;
53 import net.fortuna.ical4j.model.Property;
54 import net.fortuna.ical4j.model.PropertyFactory;
55 import net.fortuna.ical4j.model.PropertyFactoryRegistry;
56 import net.fortuna.ical4j.model.TimeZone;
57 import net.fortuna.ical4j.model.TimeZoneRegistry;
58 import net.fortuna.ical4j.model.TimeZoneRegistryFactory;
59 import net.fortuna.ical4j.model.component.VAvailability;
60 import net.fortuna.ical4j.model.component.VEvent;
61 import net.fortuna.ical4j.model.component.VTimeZone;
62 import net.fortuna.ical4j.model.component.VToDo;
63 import net.fortuna.ical4j.model.parameter.TzId;
64 import net.fortuna.ical4j.model.property.DateListProperty;
65 import net.fortuna.ical4j.model.property.DateProperty;
66 import net.fortuna.ical4j.model.property.XProperty;
67 import net.fortuna.ical4j.util.CompatibilityHints;
68 import net.fortuna.ical4j.util.Constants;
69 import net.fortuna.ical4j.util.Strings;
70
71 import org.apache.commons.logging.Log;
72 import org.apache.commons.logging.LogFactory;
73
74 /**
75 * Parses and builds an iCalendar model from an input stream. Note that this class is not thread-safe.
76 * @version 2.0
77 * @author Ben Fortuna
78 *
79 * <pre>
80 * $Id$
81 *
82 * Created: Apr 5, 2004
83 * </pre>
84 *
85 */
86 public class CalendarBuilder {
87
88 private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
89
90 private final CalendarParser parser;
91
92 private final ContentHandler contentHandler;
93
94 private final TimeZoneRegistry tzRegistry;
95
96 private List datesMissingTimezones;
97
98 /**
99 * The calendar instance created by the builder.
100 */
101 protected Calendar calendar;
102
103 /**
104 * The current component instance created by the builder.
105 */
106 protected Component component;
107
108 /**
109 * The current sub-component instance created by the builder.
110 */
111 protected Component subComponent;
112
113 /**
114 * The current property instance created by the builder.
115 */
116 protected Property property;
117
118 /**
119 * Default constructor.
120 */
121 public CalendarBuilder() {
122 this(CalendarParserFactory.getInstance().createParser(), new PropertyFactoryRegistry(),
123 new ParameterFactoryRegistry(), TimeZoneRegistryFactory.getInstance().createRegistry());
124 }
125
126 /**
127 * Constructs a new calendar builder using the specified calendar parser.
128 * @param parser a calendar parser used to parse calendar files
129 */
130 public CalendarBuilder(final CalendarParser parser) {
131 this(parser, new PropertyFactoryRegistry(), new ParameterFactoryRegistry(),
132 TimeZoneRegistryFactory.getInstance().createRegistry());
133 }
134
135 /**
136 * Constructs a new calendar builder using the specified timezone registry.
137 * @param tzRegistry a timezone registry to populate with discovered timezones
138 */
139 public CalendarBuilder(final TimeZoneRegistry tzRegistry) {
140 this(CalendarParserFactory.getInstance().createParser(), new PropertyFactoryRegistry(),
141 new ParameterFactoryRegistry(), tzRegistry);
142 }
143
144 /**
145 * Constructs a new instance using the specified parser and registry.
146 * @param parser a calendar parser used to construct the calendar
147 * @param tzRegistry a timezone registry used to retrieve {@link TimeZone}s and
148 * register additional timezone information found
149 * in the calendar
150 */
151 public CalendarBuilder(CalendarParser parser, TimeZoneRegistry tzRegistry) {
152 this(parser, new PropertyFactoryRegistry(), new ParameterFactoryRegistry(), tzRegistry);
153 }
154
155 /**
156 * @param parser a custom calendar parser
157 * @param propertyFactoryRegistry registry for non-standard property factories
158 * @param parameterFactoryRegistry registry for non-standard parameter factories
159 * @param tzRegistry a custom timezone registry
160 */
161 public CalendarBuilder(CalendarParser parser, PropertyFactoryRegistry propertyFactoryRegistry,
162 ParameterFactoryRegistry parameterFactoryRegistry, TimeZoneRegistry tzRegistry) {
163
164 this.parser = parser;
165 this.tzRegistry = tzRegistry;
166 this.contentHandler = new ContentHandlerImpl(ComponentFactory.getInstance(),
167 propertyFactoryRegistry, parameterFactoryRegistry);
168 }
169
170 /**
171 * Builds an iCalendar model from the specified input stream.
172 * @param in an input stream to read calendar data from
173 * @return a calendar parsed from the specified input stream
174 * @throws IOException where an error occurs reading data from the specified stream
175 * @throws ParserException where an error occurs parsing data from the stream
176 */
177 public Calendar build(final InputStream in) throws IOException,
178 ParserException {
179 return build(new InputStreamReader(in, DEFAULT_CHARSET));
180 }
181
182 /**
183 * Builds an iCalendar model from the specified reader. An <code>UnfoldingReader</code> is applied to the
184 * specified reader to ensure the data stream is correctly unfolded where appropriate.
185 * @param in a reader to read calendar data from
186 * @return a calendar parsed from the specified reader
187 * @throws IOException where an error occurs reading data from the specified reader
188 * @throws ParserException where an error occurs parsing data from the reader
189 */
190 public Calendar build(final Reader in) throws IOException, ParserException {
191 return build(new UnfoldingReader(in));
192 }
193
194 /**
195 * Build an iCalendar model by parsing data from the specified reader.
196 * @param uin an unfolding reader to read data from
197 * @return a calendar parsed from the specified reader
198 * @throws IOException where an error occurs reading data from the specified reader
199 * @throws ParserException where an error occurs parsing data from the reader
200 */
201 public Calendar build(final UnfoldingReader uin) throws IOException,
202 ParserException {
203 // re-initialise..
204 calendar = null;
205 component = null;
206 subComponent = null;
207 property = null;
208 datesMissingTimezones = new ArrayList();
209
210 parser.parse(uin, contentHandler);
211
212 if (datesMissingTimezones.size() > 0 && tzRegistry != null) {
213 resolveTimezones();
214 }
215
216 return calendar;
217 }
218
219 private class ContentHandlerImpl implements ContentHandler {
220
221 private final ComponentFactory componentFactory;
222
223 private final PropertyFactory propertyFactory;
224
225 private final ParameterFactory parameterFactory;
226
227 public ContentHandlerImpl(ComponentFactory componentFactory, PropertyFactory propertyFactory,
228 ParameterFactory parameterFactory) {
229
230 this.componentFactory = componentFactory;
231 this.propertyFactory = propertyFactory;
232 this.parameterFactory = parameterFactory;
233 }
234
235 public void endCalendar() {
236 // do nothing..
237 }
238
239 public void endComponent(final String name) {
240 assertComponent(component);
241
242 if (subComponent != null) {
243 if (component instanceof VTimeZone) {
244 ((VTimeZone) component).getObservances().add(subComponent);
245 }
246 else if (component instanceof VEvent) {
247 ((VEvent) component).getAlarms().add(subComponent);
248 }
249 else if (component instanceof VToDo) {
250 ((VToDo) component).getAlarms().add(subComponent);
251 }
252 else if (component instanceof VAvailability) {
253 ((VAvailability) component).getAvailable().add(subComponent);
254 }
255 subComponent = null;
256 }
257 else {
258 calendar.getComponents().add(component);
259 if (component instanceof VTimeZone && tzRegistry != null) {
260 // register the timezone for use with iCalendar objects..
261 tzRegistry.register(new TimeZone((VTimeZone) component));
262 }
263 component = null;
264 }
265 }
266
267 public void endProperty(final String name) {
268 assertProperty(property);
269
270 // replace with a constant instance if applicable..
271 property = Constants.forProperty(property);
272 if (component != null) {
273 if (subComponent != null) {
274 subComponent.getProperties().add(property);
275 }
276 else {
277 component.getProperties().add(property);
278 }
279 }
280 else if (calendar != null) {
281 calendar.getProperties().add(property);
282 }
283
284 property = null;
285 }
286
287 public void parameter(final String name, final String value) throws URISyntaxException {
288 assertProperty(property);
289
290 // parameter names are case-insensitive, but convert to upper case to simplify further processing
291 final Parameter param = parameterFactory.createParameter(name.toUpperCase(), Strings.escapeNewline(value));
292 property.getParameters().add(param);
293 if (param instanceof TzId && tzRegistry != null && !(property instanceof XProperty)) {
294 final TimeZone timezone = tzRegistry.getTimeZone(param.getValue());
295 if (timezone != null) {
296 updateTimeZone(property, timezone);
297 } else {
298 // VTIMEZONE may be defined later, so so keep
299 // track of dates until all components have been
300 // parsed, and then try again later
301 datesMissingTimezones.add(property);
302 }
303 }
304 }
305
306 /**
307 * {@inheritDoc}
308 */
309 public void propertyValue(final String value) throws URISyntaxException,
310 ParseException, IOException {
311
312 assertProperty(property);
313
314 if (property instanceof Escapable) {
315 property.setValue(Strings.unescape(value));
316 }
317 else {
318 property.setValue(value);
319 }
320 }
321
322 /**
323 * {@inheritDoc}
324 */
325 public void startCalendar() {
326 calendar = new Calendar();
327 }
328
329 /**
330 * {@inheritDoc}
331 */
332 public void startComponent(final String name) {
333 if (component != null) {
334 subComponent = componentFactory.createComponent(name);
335 }
336 else {
337 component = componentFactory.createComponent(name);
338 }
339 }
340
341 /**
342 * {@inheritDoc}
343 */
344 public void startProperty(final String name) {
345 // property names are case-insensitive, but convert to upper case to simplify further processing
346 property = propertyFactory.createProperty(name.toUpperCase());
347 }
348 }
349
350 private void assertComponent(Component component) {
351 if (component == null) {
352 throw new CalendarException("Expected component not initialised");
353 }
354 }
355
356 private void assertProperty(Property property) {
357 if (property == null) {
358 throw new CalendarException("Expected property not initialised");
359 }
360 }
361
362 /**
363 * Returns the timezone registry used in the construction of calendars.
364 * @return a timezone registry
365 */
366 public final TimeZoneRegistry getRegistry() {
367 return tzRegistry;
368 }
369
370 private void updateTimeZone(Property property, TimeZone timezone) {
371 try {
372 ((DateProperty) property).setTimeZone(timezone);
373 }
374 catch (ClassCastException e) {
375 try {
376 ((DateListProperty) property).setTimeZone(timezone);
377 }
378 catch (ClassCastException e2) {
379 if (CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_PARSING)) {
380 Log log = LogFactory.getLog(CalendarBuilder.class);
381 log.warn("Error setting timezone [" + timezone.getID()
382 + "] on property [" + property.getName()
383 + "]", e);
384 }
385 else {
386 throw e2;
387 }
388 }
389 }
390 }
391
392 private void resolveTimezones()
393 throws IOException {
394
395 // Go through each property and try to resolve the TZID.
396 for (final Iterator it = datesMissingTimezones.iterator();it.hasNext();) {
397 final Property property = (Property) it.next();
398 final Parameter tzParam = property.getParameter(Parameter.TZID);
399
400 // tzParam might be null:
401 if (tzParam == null) {
402 continue;
403 }
404
405 //lookup timezone
406 final TimeZone timezone = tzRegistry.getTimeZone(tzParam.getValue());
407
408 // If timezone found, then update date property
409 if (timezone != null) {
410 // Get the String representation of date(s) as
411 // we will need this after changing the timezone
412 final String strDate = property.getValue();
413
414 // Change the timezone
415 if(property instanceof DateProperty) {
416 ((DateProperty) property).setTimeZone(timezone);
417 }
418 else if(property instanceof DateListProperty) {
419 ((DateListProperty) property).setTimeZone(timezone);
420 }
421
422 // Reset value
423 try {
424 property.setValue(strDate);
425 } catch (ParseException e) {
426 // shouldn't happen as its already been parsed
427 throw new CalendarException(e);
428 } catch (URISyntaxException e) {
429 // shouldn't happen as its already been parsed
430 throw new CalendarException(e);
431 }
432 }
433 }
434 }
435 }

mercurial