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.

     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;
    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;
    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;
    71 import org.apache.commons.logging.Log;
    72 import org.apache.commons.logging.LogFactory;
    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 {
    88     private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    90     private final CalendarParser parser;
    92     private final ContentHandler contentHandler;
    94     private final TimeZoneRegistry tzRegistry;
    96     private List datesMissingTimezones;
    98     /**
    99      * The calendar instance created by the builder.
   100      */
   101     protected Calendar calendar;
   103     /**
   104      * The current component instance created by the builder.
   105      */
   106     protected Component component;
   108     /**
   109      * The current sub-component instance created by the builder.
   110      */
   111     protected Component subComponent;
   113     /**
   114      * The current property instance created by the builder.
   115      */
   116     protected Property property;
   118     /**
   119      * Default constructor.
   120      */
   121     public CalendarBuilder() {
   122         this(CalendarParserFactory.getInstance().createParser(), new PropertyFactoryRegistry(),
   123                 new ParameterFactoryRegistry(), TimeZoneRegistryFactory.getInstance().createRegistry());
   124     }
   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     }
   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     }
   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     }
   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) {
   164         this.parser = parser;
   165         this.tzRegistry = tzRegistry;
   166         this.contentHandler = new ContentHandlerImpl(ComponentFactory.getInstance(),
   167                 propertyFactoryRegistry, parameterFactoryRegistry);
   168     }
   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     }
   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     }
   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();
   210         parser.parse(uin, contentHandler);
   212         if (datesMissingTimezones.size() > 0 && tzRegistry != null) {
   213             resolveTimezones();
   214         }
   216         return calendar;
   217     }
   219     private class ContentHandlerImpl implements ContentHandler {
   221         private final ComponentFactory componentFactory;
   223         private final PropertyFactory propertyFactory;
   225         private final ParameterFactory parameterFactory;
   227         public ContentHandlerImpl(ComponentFactory componentFactory, PropertyFactory propertyFactory,
   228                 ParameterFactory parameterFactory) {
   230             this.componentFactory = componentFactory;
   231             this.propertyFactory = propertyFactory;
   232             this.parameterFactory = parameterFactory;
   233         }
   235         public void endCalendar() {
   236             // do nothing..
   237         }
   239         public void endComponent(final String name) {
   240             assertComponent(component);
   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         }
   267         public void endProperty(final String name) {
   268             assertProperty(property);
   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             }
   284             property = null;
   285         }
   287         public void parameter(final String name, final String value) throws URISyntaxException {
   288             assertProperty(property);
   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         }
   306         /**
   307          * {@inheritDoc}
   308          */
   309         public void propertyValue(final String value) throws URISyntaxException,
   310                 ParseException, IOException {
   312             assertProperty(property);
   314             if (property instanceof Escapable) {
   315                 property.setValue(Strings.unescape(value));
   316             }
   317             else {
   318                 property.setValue(value);
   319             }
   320         }
   322         /**
   323          * {@inheritDoc}
   324          */
   325         public void startCalendar() {
   326             calendar = new Calendar();
   327         }
   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         }
   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     }
   350     private void assertComponent(Component component) {
   351         if (component == null) {
   352             throw new CalendarException("Expected component not initialised");
   353         }
   354     }
   356     private void assertProperty(Property property) {
   357         if (property == null) {
   358             throw new CalendarException("Expected property not initialised");
   359         }
   360     }
   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     }
   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     }
   392     private void resolveTimezones() 
   393         throws IOException {
   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);
   400             // tzParam might be null: 
   401             if (tzParam == null) {
   402                 continue;
   403             }
   405             //lookup timezone
   406             final TimeZone timezone = tzRegistry.getTimeZone(tzParam.getValue());
   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();
   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                 }
   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