Java tutorial
/** * 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(); } }