com.outerspacecat.icalendar.Component.java Source code

Java tutorial

Introduction

Here is the source code for com.outerspacecat.icalendar.Component.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 java.io.IOException;
import java.io.Serializable;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Set;
import java.util.regex.Pattern;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.ThreadSafe;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableMultimap;

/**
 * A representation of an iCalendar component defined by <a
 * href="http://tools.ietf.org/html/rfc5545">RFC 5545</a>. This class does not
 * enforce the amount of times a property or sub-component is required or
 * allowed to appear.
 * 
 * @author Caleb Richardson
 */
@Immutable
@ThreadSafe
public final class Component implements Serializable {
    private final static long serialVersionUID = 1L;

    private final static Pattern NAME_PATTERN = Pattern.compile("[A-Z0-9-]+");

    private final String name;
    private final ImmutableMultimap<String, Property> properties;
    private final ImmutableMultimap<String, Component> components;

    /**
     * Creates a new component.
     * 
     * @param name the component name. Must be non {@code null} and return
     *        {@code true} for {@link #isValidName(CharSequence)}.
     * @param properties the component properties. Must be non {@code null} and
     *        all elements must be non {@code null}.
     * @param components the component sub-components. Must be non {@code null}
     *        and all elements must be non {@code null}.
     */
    public Component(final CharSequence name, final Iterable<Property> properties,
            final Iterable<Component> components) {
        Preconditions.checkNotNull(name, "name required");
        Preconditions.checkArgument(isValidName(name.toString().toUpperCase()), "invalid name: " + name);
        Preconditions.checkNotNull(properties, "properties required");
        Preconditions.checkNotNull(components, "components required");

        this.name = name.toString().toUpperCase();

        ImmutableMultimap.Builder<String, Property> propBuilder = new ImmutableMultimap.Builder<String, Property>();
        for (Property prop : properties) {
            Preconditions.checkNotNull(prop, "properties must each be non null");
            propBuilder.put(prop.getName().getName(), prop);
        }
        this.properties = propBuilder.build();

        ImmutableMultimap.Builder<String, Component> compBuilder = new ImmutableMultimap.Builder<String, Component>();
        for (Component comp : components) {
            Preconditions.checkNotNull(comp, "components must each be non null");
            compBuilder.put(comp.getName(), comp);
        }
        this.components = compBuilder.build();
    }

    /**
     * Returns the name of this component.
     * 
     * @return the name of this component. Never {@code null}, always upper case.
     */
    public String getName() {
        return name;
    }

    /**
     * Returns the properties of this component.
     * 
     * @return the properties of this component. Never {@code null}, contains zero
     *         or more entries, all keys and values non {@code null}.
     */
    public ImmutableMultimap<String, Property> getProperties() {
        return properties;
    }

    /**
     * Returns the properties of this component excluding those whose name is
     * specified in {@code names}.
     * 
     * @param names the names of the properties to exclude. Must be non
     *        {@code null}.
     * @return the properties of this component excluding those whose name is
     *         specified in {@code names}. Never {@code null}, contains zero or
     *         more entries.
     */
    public ImmutableMultimap<String, Property> getPropertiesExcept(final Set<String> names) {
        Preconditions.checkNotNull(names, "names required");

        ImmutableMultimap.Builder<String, Property> ret = ImmutableMultimap.builder();

        for (Property prop : getProperties().values()) {
            if (!names.contains(prop.getName().getName()))
                ret.put(prop.getName().getName(), prop);
        }

        return ret.build();
    }

    /**
     * Returns the sub-components of this component.
     * 
     * @return the sub-components of this component. Never {@code null}, contains
     *         zero or more entries, all keys and values non {@code null}.
     */
    public ImmutableMultimap<String, Component> getComponents() {
        return components;
    }

    /**
     * Returns the sub-components of this component excluding those whose name is
     * specified in {@code names}.
     * 
     * @param names the names of the sub-components to exclude. Must be non
     *        {@code null}.
     * @return the sub-components of this component excluding those whose name is
     *         specified in {@code names}. Never {@code null}, contains zero or
     *         more entries.
     */
    public ImmutableMultimap<String, Component> getComponentsExcept(final Set<String> names) {
        Preconditions.checkNotNull(names, "names required");

        ImmutableMultimap.Builder<String, Component> ret = ImmutableMultimap.builder();

        for (Component comp : getComponents().values()) {
            if (!names.contains(comp.getName()))
                ret.put(comp.getName(), comp);
        }

        return ret.build();
    }

    /**
     * Returns a single {@link Property} with the specified name, or {@code null}
     * if no such property exists. This method provides an easy way to ignore
     * extra properties. Which property is returned is non-deterministic, however
     * the same property will be returned each time this method is called.
     * 
     * @param name the name of the property to retrieve. Must be non {@code null}.
     * @return a single {@link Property} with the specified name, or {@code null}
     *         if no such property exists.
     */
    public Property getFirstProperty(final String name) {
        Preconditions.checkNotNull(name, "name required");

        ImmutableCollection<Property> props = getProperties().get(name.toUpperCase());
        return props.isEmpty() ? null : props.iterator().next();
    }

    /**
     * Returns a single {@link Property} with the specified name, or throws a
     * {@link CalendarParseException} if a property with the specified name does
     * not occur exactly once.
     * 
     * @param name the name of the property to retrieve. Must be non {@code null}.
     * @return a single {@link Property} with the specified name. Never
     *         {@code null}.
     * @throws CalendarParseException if a property with the specified name does
     *         not occur exactly once.
     */
    public Property getSingleProperty(final String name) throws CalendarParseException {
        Preconditions.checkNotNull(name, "name required");

        ImmutableCollection<Property> props = getProperties().get(name.toUpperCase());
        if (props.isEmpty())
            throw new CalendarParseException("no properties for name: " + name);
        if (props.size() > 1)
            throw new CalendarParseException("more than one property for name: " + name);
        return props.iterator().next();
    }

    /**
     * Converts this object to iCalendar and writes it to {@code sink}.
     * 
     * @param sink the character sink to write the generated iCalendar to. Must be
     *        non {@code null}.
     * @throws IOException if an error occurs while writing to {@code sink}
     */
    public void toICalendar(final Appendable sink) throws IOException {
        Preconditions.checkNotNull(sink, "sink required");

        sink.append("BEGIN:").append(getName()).append("\r\n");
        for (Property p : getProperties().values())
            sink.append(p.toICalendar()).append("\r\n");
        for (Component c : getComponents().values())
            c.toICalendar(sink);
        sink.append("END:").append(getName()).append("\r\n");
    }

    /**
     * Returns whether or not {@code name} is a valid component name.
     * 
     * @param name the name to test. Must be non {@code null}.
     * @return whether or not {@code name} is a valid component name
     */
    public static boolean isValidName(final CharSequence name) {
        Preconditions.checkNotNull(name, "name required");

        return NAME_PATTERN.matcher(name).matches() && !name.toString().equalsIgnoreCase("VCALENDAR");
    }

    /**
     * A builder for creating instances of {@link Component}.
     */
    final static class Builder {
        private String name;
        private final Deque<Property> properties = new ArrayDeque<Property>();
        private final Deque<Component> components = new ArrayDeque<Component>();

        /**
         * Specifies the name of the component.
         * 
         * @param name the name of the component. Must be non {@code null}.
         * @return this builder. Never {@code null}.
         */
        public Builder setName(final CharSequence name) {
            Preconditions.checkState(this.name == null, "name already set");
            Preconditions.checkNotNull(name, "name required");
            Preconditions.checkArgument(isValidName(name.toString().toUpperCase()), "invalid name: " + name);

            this.name = name.toString().toUpperCase();
            return this;
        }

        /**
         * Returns the name of the component.
         * 
         * @return the name of the component. May be {@code null}.
         */
        public String getName() {
            return name;
        }

        /**
         * Adds a property to the component.
         * 
         * @param prop the property to add. Must be non {@code null}.
         * @return this builder. Never {@code null}.
         */
        public Builder addProperty(final Property prop) {
            Preconditions.checkNotNull(prop, "prop required");

            properties.add(prop);
            return this;
        }

        /**
         * Adds a sub-component to the component.
         * 
         * @param comp the sub-component to add. Must be non {@code null}.
         * @return this builder. Never {@code null}.
         */
        public Builder addComponent(final Component comp) {
            Preconditions.checkNotNull(comp, "comp required");

            components.add(comp);
            return this;
        }

        /**
         * Builds the component. {@link #setName(CharSequence)} must have been
         * previously called.
         * 
         * @return a new component. Never {@code null}.
         */
        public Component build() {
            Preconditions.checkState(name != null, "name not set");

            return new Component(name, properties, components);
        }
    }
}