com.outerspacecat.icalendar.VEvent.java Source code

Java tutorial

Introduction

Here is the source code for com.outerspacecat.icalendar.VEvent.java

Source

/**
 * Copyright 2011 Caleb Richardson
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.outerspacecat.icalendar;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import java.io.Serializable;
import java.time.Instant;
import java.time.ZoneOffset;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.ThreadSafe;

/**
 * A representation of an iCalendar VEVENT component defined by <a
 * href="http://tools.ietf.org/html/rfc5545">RFC 5545</a>.
 * 
 * @author Caleb Richardson
 */
@Immutable
@ThreadSafe
public final class VEvent implements HasComponent, Serializable {
    private final static long serialVersionUID = 1L;

    private final static ImmutableSet<String> RESTRICTED_PROPERTY_NAMES = ImmutableSet.of("UID", "SEQUENCE",
            "DTSTAMP", "LAST-MODIFIED", "CREATED", "DTSTART", "DTEND", "DURATION", "RECURRENCE-ID", "RRULE",
            "RDATE", "EXDATE", "ORGANIZER", "STATUS", "SUMMARY", "DESCRIPTION", "LOCATION", "CLASS", "TRANSP",
            "PRIORITY", "URL", "GEO", "ATTENDEE", "ATTACH", "CATEGORIES", "COMMENT", "CONTACT", "RESOURCES");

    private final static ImmutableSet<String> RESTRICTED_COMPONENT_NAMES = ImmutableSet.of("VALARM");

    private final TypedProperty<TextType> uid;
    private final TypedProperty<Integer> sequence;
    private final TypedProperty<Instant> dtStamp;
    private final TypedProperty<Instant> created;
    private final TypedProperty<Instant> lastModified;

    private final Schedule schedule;
    private final RecurrenceId recurrenceId;
    private final RecurrenceData recurrenceData;

    private final TypedProperty<UriType> organizer;
    private final TypedProperty<TextType> status;
    private final TypedProperty<TextType> summary;
    private final TypedProperty<TextType> description;
    private final TypedProperty<TextType> location;
    private final TypedProperty<TextType> classification;
    private final TypedProperty<Boolean> transparent;
    private final TypedProperty<Integer> priority;
    private final TypedProperty<UriType> url;
    private final TypedProperty<GeoType> geo;

    private final ImmutableSet<Attendee> attendees;

    // private final ImmutableMap<ContentUri, MeasuredContentSource> attachments;

    private final ImmutableSet<TypedProperty<ImmutableSet<TextType>>> categories;
    private final ImmutableSet<TypedProperty<TextType>> comments;
    private final ImmutableSet<TypedProperty<ImmutableSet<TextType>>> resources;
    private final ImmutableSet<TypedProperty<TextType>> contacts;

    // alarms

    private final ImmutableMultimap<String, Property> extraProperties;
    private final ImmutableMultimap<String, Component> extraComponents;

    /**
     * Creates a new VEVENT.
     * 
     * @param uid the uid. Must be non {@code null}.
     * @param sequence the sequence number. May be {@code null}.
     * @param dtStamp the DTSTAMP instant. Must be non {@code null}.
     * @param created the created instant. May be {@code null}.
     * @param created the last modified instant. May be {@code null}.
     * @param schedule the schedule. Must be non {@code null}.
     * @param recurrenceData the recurrence data. Must be non {@code null}.
     */
    public VEvent(final TypedProperty<TextType> uid, final TypedProperty<Integer> sequence,
            final TypedProperty<Instant> dtStamp, final TypedProperty<Instant> created,
            final TypedProperty<Instant> lastModified, final Schedule schedule, final RecurrenceData recurrenceData,
            final TypedProperty<UriType> organizer, final TypedProperty<TextType> status,
            final TypedProperty<TextType> summary, final TypedProperty<TextType> description,
            final TypedProperty<TextType> location, final TypedProperty<TextType> classification,
            final TypedProperty<Boolean> transparent, final TypedProperty<Integer> priority,
            final TypedProperty<UriType> url, final TypedProperty<GeoType> geo, final Iterable<Attendee> attendees,
            final Iterable<TypedProperty<ImmutableSet<TextType>>> categories,
            final Iterable<TypedProperty<TextType>> comments,
            final Iterable<TypedProperty<ImmutableSet<TextType>>> resources,
            final Iterable<TypedProperty<TextType>> contacts, final Iterable<Property> extraProperties,
            final Iterable<Component> extraComponents) {
        Preconditions.checkNotNull(uid, "uid required");
        Preconditions.checkNotNull(dtStamp, "dtStamp required");
        Preconditions.checkNotNull(schedule, "schedule required");
        Preconditions.checkNotNull(recurrenceData, "recurrenceData required");
        Preconditions.checkNotNull(attendees, "attendees required");
        Preconditions.checkNotNull(categories, "categories required");
        Preconditions.checkNotNull(comments, "comments required");
        Preconditions.checkNotNull(resources, "resources required");
        Preconditions.checkNotNull(contacts, "contacts required");
        Preconditions.checkNotNull(extraProperties, "extraProperties required");
        Preconditions.checkNotNull(extraComponents, "extraComponents required");

        this.uid = uid;
        this.sequence = sequence;
        this.dtStamp = dtStamp;
        this.created = created;
        this.lastModified = lastModified;
        this.schedule = schedule;
        this.recurrenceId = null;
        this.recurrenceData = recurrenceData;
        this.organizer = organizer;
        this.status = status;
        this.summary = summary;
        this.description = description;
        this.location = location;
        this.classification = classification;
        this.transparent = transparent;
        this.priority = priority;
        this.url = url;
        this.geo = geo;
        this.attendees = ImmutableSet.copyOf(attendees);
        this.categories = ImmutableSet.copyOf(categories);
        this.comments = ImmutableSet.copyOf(comments);
        this.resources = ImmutableSet.copyOf(resources);
        this.contacts = ImmutableSet.copyOf(contacts);

        ImmutableMultimap.Builder<String, Property> extraPropertiesBuilder = ImmutableMultimap.builder();
        for (Property prop : extraProperties)
            extraPropertiesBuilder.put(prop.getName().getName(), prop);
        this.extraProperties = extraPropertiesBuilder.build();

        ImmutableMultimap.Builder<String, Component> extraComponentsBuilder = ImmutableMultimap.builder();
        for (Component comp : extraComponents)
            extraComponentsBuilder.put(comp.getName(), comp);
        this.extraComponents = extraComponentsBuilder.build();
    }

    /**
     * Parses a VEVENT component.
     * 
     * @param comp the component to parse. Must be non {@code null}.
     * @return a VEVENT. Never {@code null}.
     * @throws CalendarParseException if {@code comp} does not represent a valid
     *         VEVENT
     */
    public static VEvent parse(final Component comp) throws CalendarParseException {
        Preconditions.checkNotNull(comp, "comp required");
        Preconditions.checkArgument(comp.getName().equals("VEVENT"),
                "component name must be VEVENT, saw: " + comp.getName());

        Property prop = null;

        prop = comp.getSingleProperty("DTSTAMP");
        ParsedDateTime rawDtStamp = prop.asDateTime();
        if (!rawDtStamp.isUtc())
            throw new CalendarParseException("DTSTAMP property value must be UTC");
        TypedProperty<Instant> dtStamp = new TypedProperty<>(
                rawDtStamp.getDateTime().atZone(ZoneOffset.UTC).toInstant(), prop.getParameters().values());

        prop = comp.getSingleProperty("UID");
        TypedProperty<TextType> uid = new TypedProperty<>(prop.asText(), prop.getParameters().values());

        prop = comp.getFirstProperty("SEQUENCE");
        TypedProperty<Integer> sequence = null;
        if (prop != null) {
            int rawSequence = prop.asInteger();
            if (rawSequence < 0)
                throw new CalendarParseException("invalid SEQUENCE: " + rawSequence);
            sequence = new TypedProperty<>(rawSequence, prop.getParameters().values());
        }

        prop = comp.getFirstProperty("CREATED");
        TypedProperty<Instant> created = null;
        if (prop != null) {
            ParsedDateTime rawCreated = prop.asDateTime();
            if (!rawCreated.isUtc())
                throw new CalendarParseException("CREATED property value must be UTC");
            created = new TypedProperty<>(rawCreated.getDateTime().atZone(ZoneOffset.UTC).toInstant(),
                    prop.getParameters().values());
        }

        prop = comp.getFirstProperty("LAST-MODIFIED");
        TypedProperty<Instant> lastModified = null;
        if (prop != null) {
            ParsedDateTime rawLastModified = prop.asDateTime();
            if (!rawLastModified.isUtc())
                throw new CalendarParseException("LAST-MODIFIED property value must be UTC");
            lastModified = new TypedProperty<>(rawLastModified.getDateTime().atZone(ZoneOffset.UTC).toInstant(),
                    prop.getParameters().values());
        }

        Schedule schedule = Schedule.parse(comp.getSingleProperty("DTSTART"), comp.getFirstProperty("DTEND"),
                comp.getFirstProperty("DURATION"));

        ImmutableSet.Builder<RRule> rRuleBuilder = ImmutableSet.builder();
        for (Property rRuleProp : comp.getProperties().get("RRULE"))
            rRuleBuilder.add(RRule.parse(rRuleProp));
        ImmutableSet<RRule> rRules = rRuleBuilder.build();

        ImmutableSet.Builder<RDate> rDateBuilder = ImmutableSet.builder();
        for (Property rDateProp : comp.getProperties().get("RDATE"))
            rDateBuilder.add(RDate.parse(rDateProp));
        ImmutableSet<RDate> rDates = rDateBuilder.build();

        ImmutableSet.Builder<ExDate> exDateBuilder = ImmutableSet.builder();
        for (Property exDateProp : comp.getProperties().get("EXDATE"))
            exDateBuilder.add(ExDate.parse(exDateProp));
        ImmutableSet<ExDate> exDates = exDateBuilder.build();

        RecurrenceData recurrenceData = new RecurrenceData(rRules, rDates, exDates);

        prop = comp.getFirstProperty("ORGANIZER");
        TypedProperty<UriType> organizer = null;
        if (prop != null)
            organizer = new TypedProperty<>(prop.asUri(), prop.getParameters().values());

        prop = comp.getFirstProperty("STATUS");
        TypedProperty<TextType> status = null;
        if (prop != null)
            status = new TypedProperty<>(prop.asText(), prop.getParameters().values());

        prop = comp.getFirstProperty("SUMMARY");
        TypedProperty<TextType> summary = null;
        if (prop != null)
            summary = new TypedProperty<>(prop.asText(), prop.getParameters().values());

        prop = comp.getFirstProperty("DESCRIPTION");
        TypedProperty<TextType> description = null;
        if (prop != null)
            description = new TypedProperty<>(prop.asText(), prop.getParameters().values());

        prop = comp.getFirstProperty("LOCATION");
        TypedProperty<TextType> location = null;
        if (prop != null)
            location = new TypedProperty<>(prop.asText(), prop.getParameters().values());

        prop = comp.getFirstProperty("CLASS");
        TypedProperty<TextType> classification = null;
        if (prop != null)
            classification = new TypedProperty<>(prop.asText(), prop.getParameters().values());

        prop = comp.getFirstProperty("TRANSP");
        TypedProperty<Boolean> transparent = null;
        if (prop != null) {
            transparent = new TypedProperty<>(prop.getValue().equals("TRANSPARENT"), prop.getParameters().values());
        } else {
            transparent = new TypedProperty<>(false);
        }

        prop = comp.getFirstProperty("PRIORITY");
        TypedProperty<Integer> priority = null;
        if (prop != null) {
            int rawPriority = prop.asInteger();
            if (rawPriority < 0 || rawPriority > 9)
                throw new CalendarParseException("invalid PRIORITY: " + rawPriority);
            priority = new TypedProperty<>(rawPriority, prop.getParameters().values());
        }

        prop = comp.getFirstProperty("URL");
        TypedProperty<UriType> url = null;
        if (prop != null)
            url = new TypedProperty<>(prop.asUri(), prop.getParameters().values());

        prop = comp.getFirstProperty("GEO");
        TypedProperty<GeoType> geo = null;
        if (prop != null)
            geo = new TypedProperty<>(prop.asGeo(), prop.getParameters().values());

        ImmutableSet.Builder<Attendee> attendeesBuilder = ImmutableSet.builder();
        for (Property attendeeProp : comp.getProperties().get("ATTENDEE"))
            attendeesBuilder.add(Attendee.parse(attendeeProp));
        ImmutableSet<Attendee> attendees = attendeesBuilder.build();

        ImmutableSet.Builder<TypedProperty<ImmutableSet<TextType>>> categoriesBuilder = ImmutableSet.builder();
        for (Property categoryProp : comp.getProperties().get("CATEGORIES"))
            categoriesBuilder.add(new TypedProperty<ImmutableSet<TextType>>(categoryProp.asTextValues(),
                    categoryProp.getParameters().values()));
        ImmutableSet<TypedProperty<ImmutableSet<TextType>>> categories = categoriesBuilder.build();

        ImmutableSet.Builder<TypedProperty<TextType>> commentsBuilder = ImmutableSet.builder();
        for (Property commentProp : comp.getProperties().get("COMMENT"))
            commentsBuilder
                    .add(new TypedProperty<TextType>(commentProp.asText(), commentProp.getParameters().values()));
        ImmutableSet<TypedProperty<TextType>> comments = commentsBuilder.build();

        ImmutableSet.Builder<TypedProperty<ImmutableSet<TextType>>> resourcesBuilder = ImmutableSet.builder();
        for (Property resourceProp : comp.getProperties().get("RESOURCES"))
            resourcesBuilder.add(new TypedProperty<ImmutableSet<TextType>>(resourceProp.asTextValues(),
                    resourceProp.getParameters().values()));
        ImmutableSet<TypedProperty<ImmutableSet<TextType>>> resources = resourcesBuilder.build();

        ImmutableSet.Builder<TypedProperty<TextType>> contactsBuilder = ImmutableSet.builder();
        for (Property contactProp : comp.getProperties().get("CONTACT"))
            contactsBuilder
                    .add(new TypedProperty<TextType>(contactProp.asText(), contactProp.getParameters().values()));
        ImmutableSet<TypedProperty<TextType>> contacts = contactsBuilder.build();

        return new VEvent(uid, sequence, dtStamp, created, lastModified, schedule, recurrenceData, organizer,
                status, summary, description, location, classification, transparent, priority, url, geo, attendees,
                categories, comments, resources, contacts,
                comp.getPropertiesExcept(RESTRICTED_PROPERTY_NAMES).values(),
                comp.getComponentsExcept(RESTRICTED_COMPONENT_NAMES).values());
    }

    /**
     * The UID property.
     * 
     * @return the UID property. Never {@code null}.
     */
    public TypedProperty<TextType> getUid() {
        return uid;
    }

    /**
     * The SEQUENCE property.
     * 
     * @return the SEQUENCE property. May be {@code null}.
     */
    public TypedProperty<Integer> getSequence() {
        return sequence;
    }

    /**
     * The DTSTAMP property.
     * 
     * @return the DTSTAMP property. Never {@code null}.
     */
    public TypedProperty<Instant> getDtStamp() {
        return dtStamp;
    }

    /**
     * The CREATED property.
     * 
     * @return the CREATED property. May be {@code null}.
     */
    public TypedProperty<Instant> getCreated() {
        return created;
    }

    /**
     * The LAST-MODIFIED property.
     * 
     * @return the LAST-MODIFIED property. May be {@code null}.
     */
    public TypedProperty<Instant> getLastModified() {
        return lastModified;
    }

    /**
     * The schedule represented by the DTSTART, DTEND, and DURATION properties..
     * 
     * @return the schedule. Never {@code null}.
     */
    public Schedule getSchedule() {
        return schedule;
    }

    /**
     * The recurrence data represented by the RRULE, RDATE, and EXDATE properties.
     * 
     * @return the recurrence data. Never {@code null}. May be empty of any data.
     */
    public RecurrenceData getRecurrenceData() {
        return recurrenceData;
    }

    /**
     * The RECURRENCE-ID property.
     * 
     * @return the RECURRENCE-ID property. May be {@code null}.
     */
    public RecurrenceId getRecurrenceId() {
        return recurrenceId;
    }

    /**
     * The ORGANIZER property.
     * 
     * @return the ORGANIZER property. May be {@code null}.
     */
    public TypedProperty<UriType> getOrganizer() {
        return organizer;
    }

    /**
     * The STATUS property.
     * 
     * @return the STATUS property. May be {@code null}.
     */
    public TypedProperty<TextType> getStatus() {
        return status;
    }

    /**
     * The SUMMARY property.
     * 
     * @return the SUMMARY property. May be {@code null}.
     */
    public TypedProperty<TextType> getSummary() {
        return summary;
    }

    /**
     * The DESCRIPTION property.
     * 
     * @return the DESCRIPTION property. May be {@code null}.
     */
    public TypedProperty<TextType> getDescription() {
        return description;
    }

    /**
     * The LOCATION property.
     * 
     * @return the LOCATION property. May be {@code null}.
     */
    public TypedProperty<TextType> getLocation() {
        return location;
    }

    /**
     * The CLASSIFICATION property.
     * 
     * @return the CLASSIFICATION property. May be {@code null}.
     */
    public TypedProperty<TextType> getClassification() {
        return classification;
    }

    /**
     * The TRANSP property.
     * 
     * @return the TRANSP property. May be {@code null}.
     */
    public TypedProperty<Boolean> isTransparent() {
        return transparent;
    }

    /**
     * The PRIORITY property.
     * 
     * @return the PRIORITY property. May be {@code null}.
     */
    public TypedProperty<Integer> getPriority() {
        return priority;
    }

    /**
     * The URL property.
     * 
     * @return the URL property. May be {@code null}.
     */
    public TypedProperty<UriType> getUrl() {
        return url;
    }

    /**
     * The GEO property.
     * 
     * @return the GEO property. May be {@code null}.
     */
    public TypedProperty<GeoType> getGeo() {
        return geo;
    }

    /**
     * Returns the attendees of this event.
     * 
     * @return the attendees of this event. Never {@code null}, may be empty.
     */
    public ImmutableSet<Attendee> getAttendees() {
        return attendees;
    }

    /**
     * Returns the categories of this event.
     * 
     * @return the categories of this event. Never {@code null}, may be empty.
     */
    public ImmutableSet<TypedProperty<ImmutableSet<TextType>>> getCategories() {
        return categories;
    }

    /**
     * Returns the comments of this event.
     * 
     * @return the comments of this event. Never {@code null}, may be empty.
     */
    public ImmutableSet<TypedProperty<TextType>> getComments() {
        return comments;
    }

    /**
     * Returns the resources of this event.
     * 
     * @return the resources of this event. Never {@code null}, may be empty.
     */
    public ImmutableSet<TypedProperty<ImmutableSet<TextType>>> getResources() {
        return resources;
    }

    /**
     * Returns the contacts of this event.
     * 
     * @return the contacts of this event. Never {@code null}, may be empty.
     */
    public ImmutableSet<TypedProperty<TextType>> getContacts() {
        return contacts;
    }

    /**
     * Returns the extra properties of this event.
     * 
     * @return the extra parameters of this event. Never {@code null}, may be
     *         empty.
     */
    public ImmutableMultimap<String, Property> getExtraProperties() {
        return extraProperties;
    }

    /**
     * Returns the extra components of this event.
     * 
     * @return the extra components of this event. Never {@code null}, may be
     *         empty.
     */
    public ImmutableMultimap<String, Component> getExtraComponents() {
        return extraComponents;
    }

    @Override
    public Component toComponent(final HasTimeZones timeZones) {
        Preconditions.checkNotNull(timeZones, "timeZones required");
        Component.Builder builder = new Component.Builder();

        builder.setName("VEVENT");

        for (Property prop : getExtraProperties().values())
            builder.addProperty(prop);

        for (Component comp : getExtraComponents().values())
            builder.addComponent(comp);

        return builder.build();
    }
}