michael@0: /** michael@0: * Copyright (c) 2012, Ben Fortuna michael@0: * All rights reserved. michael@0: * michael@0: * Redistribution and use in source and binary forms, with or without michael@0: * modification, are permitted provided that the following conditions michael@0: * are met: michael@0: * michael@0: * o Redistributions of source code must retain the above copyright michael@0: * notice, this list of conditions and the following disclaimer. michael@0: * michael@0: * o Redistributions in binary form must reproduce the above copyright michael@0: * notice, this list of conditions and the following disclaimer in the michael@0: * documentation and/or other materials provided with the distribution. michael@0: * michael@0: * o Neither the name of Ben Fortuna nor the names of any other contributors michael@0: * may be used to endorse or promote products derived from this software michael@0: * without specific prior written permission. michael@0: * michael@0: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR michael@0: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, michael@0: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, michael@0: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR michael@0: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF michael@0: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING michael@0: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS michael@0: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: */ michael@0: package net.fortuna.ical4j.util; michael@0: michael@0: import java.io.FileInputStream; michael@0: import java.io.IOException; michael@0: import java.net.URL; michael@0: import java.nio.charset.Charset; michael@0: import java.util.HashMap; michael@0: import java.util.Iterator; michael@0: import java.util.Map; michael@0: michael@0: import net.fortuna.ical4j.data.CalendarBuilder; michael@0: import net.fortuna.ical4j.data.ParserException; michael@0: import net.fortuna.ical4j.model.Calendar; michael@0: import net.fortuna.ical4j.model.Component; michael@0: import net.fortuna.ical4j.model.ComponentList; michael@0: import net.fortuna.ical4j.model.ConstraintViolationException; michael@0: import net.fortuna.ical4j.model.IndexedComponentList; michael@0: import net.fortuna.ical4j.model.Parameter; michael@0: import net.fortuna.ical4j.model.Property; michael@0: import net.fortuna.ical4j.model.component.VTimeZone; michael@0: import net.fortuna.ical4j.model.parameter.TzId; michael@0: import net.fortuna.ical4j.model.property.Method; michael@0: import net.fortuna.ical4j.model.property.Uid; michael@0: michael@0: /** michael@0: * $Id$ michael@0: * michael@0: * Created on 10/11/2006 michael@0: * michael@0: * Utility method for working with {@link Calendar}s. michael@0: * @author Ben Fortuna michael@0: */ michael@0: public final class Calendars { michael@0: michael@0: /** michael@0: * Constructor made private to enforce static nature. michael@0: */ michael@0: private Calendars() { michael@0: } michael@0: michael@0: /** michael@0: * Loads a calendar from the specified file. michael@0: * @param filename the name of the file from which to load calendar data michael@0: * @return returns a new calendar instance initialised from the specified file michael@0: * @throws IOException occurs when there is an error reading the specified file michael@0: * @throws ParserException occurs when the data in the specified file is invalid michael@0: */ michael@0: public static Calendar load(final String filename) throws IOException, ParserException { michael@0: final FileInputStream fin = new FileInputStream(filename); michael@0: final CalendarBuilder builder = new CalendarBuilder(); michael@0: return builder.build(fin); michael@0: } michael@0: michael@0: /** michael@0: * Loads a calendar from the specified URL. michael@0: * @param url the URL from which to load calendar data michael@0: * @return returns a new calendar instance initialised from the specified URL michael@0: * @throws IOException occurs when there is an error reading from the specified URL michael@0: * @throws ParserException occurs when the data in the specified URL is invalid michael@0: */ michael@0: public static Calendar load(final URL url) throws IOException, ParserException { michael@0: final CalendarBuilder builder = new CalendarBuilder(); michael@0: return builder.build(url.openStream()); michael@0: } michael@0: michael@0: /** michael@0: * Merge all properties and components from two specified calendars into one instance. michael@0: * Note that the merge process is not very sophisticated, and may result in invalid calendar michael@0: * data (e.g. multiple properties of a type that should only be specified once). michael@0: * @param c1 the first calendar to merge michael@0: * @param c2 the second calendar to merge michael@0: * @return a Calendar instance containing all properties and components from both of the specified calendars michael@0: */ michael@0: public static Calendar merge(final Calendar c1, final Calendar c2) { michael@0: final Calendar result = new Calendar(); michael@0: result.getProperties().addAll(c1.getProperties()); michael@0: for (final Iterator i = c2.getProperties().iterator(); i.hasNext();) { michael@0: final Property p = (Property) i.next(); michael@0: if (!result.getProperties().contains(p)) { michael@0: result.getProperties().add(p); michael@0: } michael@0: } michael@0: result.getComponents().addAll(c1.getComponents()); michael@0: for (final Iterator i = c2.getComponents().iterator(); i.hasNext();) { michael@0: final Component c = (Component) i.next(); michael@0: if (!result.getComponents().contains(c)) { michael@0: result.getComponents().add(c); michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: /** michael@0: * Wraps a component in a calendar. michael@0: * @param component the component to wrap with a calendar michael@0: * @return a calendar containing the specified component michael@0: */ michael@0: public static Calendar wrap(final Component component) { michael@0: final ComponentList components = new ComponentList(); michael@0: components.add(component); michael@0: return new Calendar(components); michael@0: } michael@0: michael@0: /** michael@0: * Splits a calendar object into distinct calendar objects for unique michael@0: * identifers (UID). michael@0: * @param calendar a calendar instance michael@0: * @return an array of calendar objects michael@0: */ michael@0: public static Calendar[] split(final Calendar calendar) { michael@0: // if calendar contains one component or less, or is composed entirely of timezone michael@0: // definitions, return the original calendar unmodified.. michael@0: if (calendar.getComponents().size() <= 1 michael@0: || calendar.getComponents(Component.VTIMEZONE).size() == calendar.getComponents().size()) { michael@0: return new Calendar[] {calendar}; michael@0: } michael@0: michael@0: final IndexedComponentList timezones = new IndexedComponentList(calendar.getComponents(Component.VTIMEZONE), michael@0: Property.TZID); michael@0: michael@0: final Map calendars = new HashMap(); michael@0: for (final Iterator i = calendar.getComponents().iterator(); i.hasNext();) { michael@0: final Component c = (Component) i.next(); michael@0: if (c instanceof VTimeZone) { michael@0: continue; michael@0: } michael@0: michael@0: final Uid uid = (Uid) c.getProperty(Property.UID); michael@0: michael@0: Calendar uidCal = (Calendar) calendars.get(uid); michael@0: if (uidCal == null) { michael@0: uidCal = new Calendar(calendar.getProperties(), new ComponentList()); michael@0: // remove METHOD property for split calendars.. michael@0: for (final Iterator mp = uidCal.getProperties(Property.METHOD).iterator(); mp.hasNext();) { michael@0: uidCal.getProperties().remove(mp.next()); michael@0: } michael@0: calendars.put(uid, uidCal); michael@0: } michael@0: michael@0: for (final Iterator j = c.getProperties().iterator(); j.hasNext();) { michael@0: final Property p = (Property) j.next(); michael@0: final TzId tzid = (TzId) p.getParameter(Parameter.TZID); michael@0: if (tzid != null) { michael@0: final VTimeZone timezone = (VTimeZone) timezones.getComponent(tzid.getValue()); michael@0: if (!uidCal.getComponents().contains(timezone)) { michael@0: uidCal.getComponents().add(timezone); michael@0: } michael@0: } michael@0: } michael@0: uidCal.getComponents().add(c); michael@0: } michael@0: return (Calendar[]) calendars.values().toArray(new Calendar[calendars.values().size()]); michael@0: } michael@0: michael@0: /** michael@0: * Returns a unique identifier as specified by components in the provided calendar. michael@0: * @param calendar a calendar instance michael@0: * @return the UID property michael@0: * @throws ConstraintViolationException if zero or more than one unique identifer is found in the specified calendar michael@0: */ michael@0: public static Uid getUid(final Calendar calendar) throws ConstraintViolationException { michael@0: Uid uid = null; michael@0: for (final Iterator i = calendar.getComponents().iterator(); i.hasNext();) { michael@0: final Component c = (Component) i.next(); michael@0: for (final Iterator j = c.getProperties(Property.UID).iterator(); j.hasNext();) { michael@0: final Uid foundUid = (Uid) j.next(); michael@0: if (uid != null && !uid.equals(foundUid)) { michael@0: throw new ConstraintViolationException("More than one UID found in calendar"); michael@0: } michael@0: uid = foundUid; michael@0: } michael@0: } michael@0: if (uid == null) { michael@0: throw new ConstraintViolationException("Calendar must specify a single unique identifier (UID)"); michael@0: } michael@0: return uid; michael@0: } michael@0: michael@0: /** michael@0: * Returns an appropriate MIME Content-Type for the specified calendar object. michael@0: * @param calendar a calendar instance michael@0: * @param charset an optional encoding michael@0: * @return a content type string michael@0: */ michael@0: public static String getContentType(Calendar calendar, Charset charset) { michael@0: final StringBuffer b = new StringBuffer("text/calendar"); michael@0: michael@0: final Method method = (Method) calendar.getProperty(Property.METHOD); michael@0: if (method != null) { michael@0: b.append("; method="); michael@0: b.append(method.getValue()); michael@0: } michael@0: michael@0: if (charset != null) { michael@0: b.append("; charset="); michael@0: b.append(charset); michael@0: } michael@0: return b.toString(); michael@0: } michael@0: }