
net.fortuna.ical4j.data.CalendarBuilder Maven / Gradle / Ivy
/**
* Copyright (c) 2012, Ben Fortuna
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* o Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* o Neither the name of Ben Fortuna nor the names of any other contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.fortuna.ical4j.data;
import net.fortuna.ical4j.model.*;
import net.fortuna.ical4j.model.component.*;
import net.fortuna.ical4j.model.parameter.TzId;
import net.fortuna.ical4j.model.property.DateListProperty;
import net.fortuna.ical4j.model.property.DateProperty;
import net.fortuna.ical4j.model.property.XProperty;
import net.fortuna.ical4j.util.CompatibilityHints;
import net.fortuna.ical4j.util.Constants;
import net.fortuna.ical4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
/**
* Parses and builds an iCalendar model from an input stream. Note that this class is not thread-safe.
*
* @author Ben Fortuna
*
*
* $Id$
*
* Created: Apr 5, 2004
*
* @version 2.0
*/
public class CalendarBuilder {
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private final CalendarParser parser;
private final ContentHandler contentHandler;
private final TimeZoneRegistry tzRegistry;
private List datesMissingTimezones;
/**
* The calendar instance created by the builder.
*/
protected Calendar calendar;
/**
* The current component instance created by the builder.
*/
protected CalendarComponent component;
/**
* The current sub-component instance created by the builder.
*/
protected Component subComponent;
/**
* The current property instance created by the builder.
*/
protected Property property;
/**
* Default constructor.
*/
public CalendarBuilder() {
this(CalendarParserFactory.getInstance().createParser(), new PropertyFactoryRegistry(),
new ParameterFactoryRegistry(), TimeZoneRegistryFactory.getInstance().createRegistry());
}
/**
* Constructs a new calendar builder using the specified calendar parser.
*
* @param parser a calendar parser used to parse calendar files
*/
public CalendarBuilder(final CalendarParser parser) {
this(parser, new PropertyFactoryRegistry(), new ParameterFactoryRegistry(),
TimeZoneRegistryFactory.getInstance().createRegistry());
}
/**
* Constructs a new calendar builder using the specified timezone registry.
*
* @param tzRegistry a timezone registry to populate with discovered timezones
*/
public CalendarBuilder(final TimeZoneRegistry tzRegistry) {
this(CalendarParserFactory.getInstance().createParser(), new PropertyFactoryRegistry(),
new ParameterFactoryRegistry(), tzRegistry);
}
/**
* Constructs a new instance using the specified parser and registry.
*
* @param parser a calendar parser used to construct the calendar
* @param tzRegistry a timezone registry used to retrieve {@link TimeZone}s and
* register additional timezone information found
* in the calendar
*/
public CalendarBuilder(CalendarParser parser, TimeZoneRegistry tzRegistry) {
this(parser, new PropertyFactoryRegistry(), new ParameterFactoryRegistry(), tzRegistry);
}
/**
* @param parser a custom calendar parser
* @param propertyFactoryRegistry registry for non-standard property factories
* @param parameterFactoryRegistry registry for non-standard parameter factories
* @param tzRegistry a custom timezone registry
*/
public CalendarBuilder(CalendarParser parser, PropertyFactoryRegistry propertyFactoryRegistry,
ParameterFactoryRegistry parameterFactoryRegistry, TimeZoneRegistry tzRegistry) {
this.parser = parser;
this.tzRegistry = tzRegistry;
this.contentHandler = new ContentHandlerImpl(ComponentFactoryImpl.getInstance(),
propertyFactoryRegistry, parameterFactoryRegistry);
}
/**
* Builds an iCalendar model from the specified input stream.
*
* @param in an input stream to read calendar data from
* @return a calendar parsed from the specified input stream
* @throws IOException where an error occurs reading data from the specified stream
* @throws ParserException where an error occurs parsing data from the stream
*/
public Calendar build(final InputStream in) throws IOException,
ParserException {
return build(new InputStreamReader(in, DEFAULT_CHARSET));
}
/**
* Builds an iCalendar model from the specified reader. An UnfoldingReader
is applied to the
* specified reader to ensure the data stream is correctly unfolded where appropriate.
*
* @param in a reader to read calendar data from
* @return a calendar parsed from the specified reader
* @throws IOException where an error occurs reading data from the specified reader
* @throws ParserException where an error occurs parsing data from the reader
*/
public Calendar build(final Reader in) throws IOException, ParserException {
return build(new UnfoldingReader(in));
}
/**
* Build an iCalendar model by parsing data from the specified reader.
*
* @param uin an unfolding reader to read data from
* @return a calendar parsed from the specified reader
* @throws IOException where an error occurs reading data from the specified reader
* @throws ParserException where an error occurs parsing data from the reader
*/
public Calendar build(final UnfoldingReader uin) throws IOException,
ParserException {
// re-initialise..
calendar = null;
component = null;
subComponent = null;
property = null;
datesMissingTimezones = new ArrayList();
parser.parse(uin, contentHandler);
if (datesMissingTimezones.size() > 0 && tzRegistry != null) {
resolveTimezones();
}
return calendar;
}
private class ContentHandlerImpl implements ContentHandler {
private final ComponentFactoryImpl componentFactory;
private final PropertyFactoryRegistry propertyFactory;
private final ParameterFactoryRegistry parameterFactory;
public ContentHandlerImpl(ComponentFactoryImpl componentFactory, PropertyFactoryRegistry propertyFactory,
ParameterFactoryRegistry parameterFactory) {
this.componentFactory = componentFactory;
this.propertyFactory = propertyFactory;
this.parameterFactory = parameterFactory;
}
public void endCalendar() {
// do nothing..
}
public void endComponent(final String name) {
assertComponent(component);
if (subComponent != null) {
if (component instanceof VTimeZone) {
((VTimeZone) component).getObservances().add((Observance) subComponent);
} else if (component instanceof VEvent) {
((VEvent) component).getAlarms().add((VAlarm) subComponent);
} else if (component instanceof VToDo) {
((VToDo) component).getAlarms().add((VAlarm) subComponent);
} else if (component instanceof VAvailability) {
((VAvailability) component).getAvailable().add((Available) subComponent);
}
subComponent = null;
} else {
calendar.getComponents().add(component);
if (component instanceof VTimeZone && tzRegistry != null) {
// register the timezone for use with iCalendar objects..
tzRegistry.register(new TimeZone((VTimeZone) component));
}
component = null;
}
}
public void endProperty(final String name) {
assertProperty(property);
// replace with a constant instance if applicable..
property = Constants.forProperty(property);
if (component != null) {
if (subComponent != null) {
subComponent.getProperties().add(property);
} else {
component.getProperties().add(property);
}
} else if (calendar != null) {
calendar.getProperties().add(property);
}
property = null;
}
public void parameter(final String name, final String value) throws URISyntaxException {
assertProperty(property);
// parameter names are case-insensitive, but convert to upper case to simplify further processing
final Parameter param = parameterFactory.createParameter(name.toUpperCase(), Strings.escapeNewline(value));
property.getParameters().add(param);
if (param instanceof TzId && tzRegistry != null && !(property instanceof XProperty)) {
final TimeZone timezone = tzRegistry.getTimeZone(param.getValue());
if (timezone != null) {
updateTimeZone(property, timezone);
} else {
// VTIMEZONE may be defined later, so so keep
// track of dates until all components have been
// parsed, and then try again later
datesMissingTimezones.add(property);
}
}
}
/**
* {@inheritDoc}
*/
public void propertyValue(final String value) throws URISyntaxException,
ParseException, IOException {
assertProperty(property);
if (property instanceof Escapable) {
property.setValue(Strings.unescape(value));
} else {
property.setValue(value);
}
}
/**
* {@inheritDoc}
*/
public void startCalendar() {
calendar = new Calendar();
}
/**
* {@inheritDoc}
*/
public void startComponent(final String name) {
if (component != null) {
subComponent = componentFactory.createComponent(name);
} else {
component = componentFactory.createComponent(name);
}
}
/**
* {@inheritDoc}
*/
public void startProperty(final String name) {
// property names are case-insensitive, but convert to upper case to simplify further processing
property = propertyFactory.createProperty(name.toUpperCase());
}
}
private void assertComponent(Component component) {
if (component == null) {
throw new CalendarException("Expected component not initialised");
}
}
private void assertProperty(Property property) {
if (property == null) {
throw new CalendarException("Expected property not initialised");
}
}
/**
* Returns the timezone registry used in the construction of calendars.
*
* @return a timezone registry
*/
public final TimeZoneRegistry getRegistry() {
return tzRegistry;
}
private void updateTimeZone(Property property, TimeZone timezone) {
try {
((DateProperty) property).setTimeZone(timezone);
} catch (ClassCastException e) {
try {
((DateListProperty) property).setTimeZone(timezone);
} catch (ClassCastException e2) {
if (CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_PARSING)) {
Logger log = LoggerFactory.getLogger(CalendarBuilder.class);
log.warn("Error setting timezone [" + timezone.getID()
+ "] on property [" + property.getName()
+ "]", e);
} else {
throw e2;
}
}
}
}
private void resolveTimezones()
throws IOException {
// Go through each property and try to resolve the TZID.
for (Property property : datesMissingTimezones) {
final Parameter tzParam = property.getParameter(Parameter.TZID);
// tzParam might be null:
if (tzParam == null) {
continue;
}
//lookup timezone
final TimeZone timezone = tzRegistry.getTimeZone(tzParam.getValue());
// If timezone found, then update date property
if (timezone != null) {
// Get the String representation of date(s) as
// we will need this after changing the timezone
final String strDate = property.getValue();
// Change the timezone
if (property instanceof DateProperty) {
((DateProperty) property).setTimeZone(timezone);
} else if (property instanceof DateListProperty) {
((DateListProperty) property).setTimeZone(timezone);
}
// Reset value
try {
property.setValue(strDate);
} catch (ParseException e) {
// shouldn't happen as its already been parsed
throw new CalendarException(e);
} catch (URISyntaxException e) {
// shouldn't happen as its already been parsed
throw new CalendarException(e);
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy