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 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); } } }