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

Tue, 10 Feb 2015 18:12:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 10 Feb 2015 18:12:00 +0100
changeset 0
fb9019fb1bf7
permissions
-rw-r--r--

Import initial revisions of existing project AndroidCaldavSyncAdapater,
forked from upstream repository at 27e8a0f8495c92e0780d450bdf0c7cec77a03a55.

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

mercurial