alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Android example source code file (ICalendar.java)

This example Android source code file (ICalendar.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Android by Example" TM.

Java - Android tags/keywords

android, arraylist, component, end, expected, formatexception, linkedhashmap, linkedlist, override, parameter, parserstate, property, set, string, stringbuilder, util, utilities, utils

The ICalendar.java Android example source code

/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * 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 android.pim;

import android.util.Log;
import android.util.Config;

import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.ArrayList;

/**
 * Parses RFC 2445 iCalendar objects.
 */
public class ICalendar {

    private static final String TAG = "Sync";

    // TODO: keep track of VEVENT, VTODO, VJOURNAL, VFREEBUSY, VTIMEZONE, VALARM
    // components, by type field or by subclass?  subclass would allow us to
    // enforce grammars.

    /**
     * Exception thrown when an iCalendar object has invalid syntax.
     */
    public static class FormatException extends Exception {
        public FormatException() {
            super();
        }

        public FormatException(String msg) {
            super(msg);
        }

        public FormatException(String msg, Throwable cause) {
            super(msg, cause);
        }
    }

    /**
     * A component within an iCalendar (VEVENT, VTODO, VJOURNAL, VFEEBUSY,
     * VTIMEZONE, VALARM).
     */
    public static class Component {

        // components
        private static final String BEGIN = "BEGIN";
        private static final String END = "END";
        private static final String NEWLINE = "\n";
        public static final String VCALENDAR = "VCALENDAR";
        public static final String VEVENT = "VEVENT";
        public static final String VTODO = "VTODO";
        public static final String VJOURNAL = "VJOURNAL";
        public static final String VFREEBUSY = "VFREEBUSY";
        public static final String VTIMEZONE = "VTIMEZONE";
        public static final String VALARM = "VALARM";

        private final String mName;
        private final Component mParent; // see if we can get rid of this
        private LinkedList<Component> mChildren = null;
        private final LinkedHashMap<String, ArrayList mPropsMap =
                new LinkedHashMap<String, ArrayList();

        /**
         * Creates a new component with the provided name.
         * @param name The name of the component.
         */
        public Component(String name, Component parent) {
            mName = name;
            mParent = parent;
        }

        /**
         * Returns the name of the component.
         * @return The name of the component.
         */
        public String getName() {
            return mName;
        }

        /**
         * Returns the parent of this component.
         * @return The parent of this component.
         */
        public Component getParent() {
            return mParent;
        }

        /**
         * Helper that lazily gets/creates the list of children.
         * @return The list of children.
         */
        protected LinkedList<Component> getOrCreateChildren() {
            if (mChildren == null) {
                mChildren = new LinkedList<Component>();
            }
            return mChildren;
        }

        /**
         * Adds a child component to this component.
         * @param child The child component.
         */
        public void addChild(Component child) {
            getOrCreateChildren().add(child);
        }

        /**
         * Returns a list of the Component children of this component.  May be
         * null, if there are no children.
         *
         * @return A list of the children.
         */
        public List<Component> getComponents() {
            return mChildren;
        }

        /**
         * Adds a Property to this component.
         * @param prop
         */
        public void addProperty(Property prop) {
            String name= prop.getName();
            ArrayList<Property> props = mPropsMap.get(name);
            if (props == null) {
                props = new ArrayList<Property>();
                mPropsMap.put(name, props);
            }
            props.add(prop);
        }

        /**
         * Returns a set of the property names within this component.
         * @return A set of property names within this component.
         */
        public Set<String> getPropertyNames() {
            return mPropsMap.keySet();
        }

        /**
         * Returns a list of properties with the specified name.  Returns null
         * if there are no such properties.
         * @param name The name of the property that should be returned.
         * @return A list of properties with the requested name.
         */
        public List<Property> getProperties(String name) {
            return mPropsMap.get(name);
        }

        /**
         * Returns the first property with the specified name.  Returns null
         * if there is no such property.
         * @param name The name of the property that should be returned.
         * @return The first property with the specified name.
         */
        public Property getFirstProperty(String name) {
            List<Property> props = mPropsMap.get(name);
            if (props == null || props.size() == 0) {
                return null;
            }
            return props.get(0);
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            toString(sb);
            sb.append(NEWLINE);
            return sb.toString();
        }

        /**
         * Helper method that appends this component to a StringBuilder.  The
         * caller is responsible for appending a newline at the end of the
         * component.
         */
        public void toString(StringBuilder sb) {
            sb.append(BEGIN);
            sb.append(":");
            sb.append(mName);
            sb.append(NEWLINE);

            // append the properties
            for (String propertyName : getPropertyNames()) {
                for (Property property : getProperties(propertyName)) {
                    property.toString(sb);
                    sb.append(NEWLINE);
                }
            }

            // append the sub-components
            if (mChildren != null) {
                for (Component component : mChildren) {
                    component.toString(sb);
                    sb.append(NEWLINE);
                }
            }

            sb.append(END);
            sb.append(":");
            sb.append(mName);
        }
    }

    /**
     * A property within an iCalendar component (e.g., DTSTART, DTEND, etc.,
     * within a VEVENT).
     */
    public static class Property {
        // properties
        // TODO: do we want to list these here?  the complete list is long.
        public static final String DTSTART = "DTSTART";
        public static final String DTEND = "DTEND";
        public static final String DURATION = "DURATION";
        public static final String RRULE = "RRULE";
        public static final String RDATE = "RDATE";
        public static final String EXRULE = "EXRULE";
        public static final String EXDATE = "EXDATE";
        // ... need to add more.
        
        private final String mName;
        private LinkedHashMap<String, ArrayList mParamsMap =
                new LinkedHashMap<String, ArrayList();
        private String mValue; // TODO: make this final?

        /**
         * Creates a new property with the provided name.
         * @param name The name of the property.
         */
        public Property(String name) {
            mName = name;
        }

        /**
         * Creates a new property with the provided name and value.
         * @param name The name of the property.
         * @param value The value of the property.
         */
        public Property(String name, String value) {
            mName = name;
            mValue = value;
        }

        /**
         * Returns the name of the property.
         * @return The name of the property.
         */
        public String getName() {
            return mName;
        }

        /**
         * Returns the value of this property.
         * @return The value of this property.
         */
        public String getValue() {
            return mValue;
        }

        /**
         * Sets the value of this property.
         * @param value The desired value for this property.
         */
        public void setValue(String value) {
            mValue = value;
        }        

        /**
         * Adds a {@link Parameter} to this property.
         * @param param The parameter that should be added.
         */
        public void addParameter(Parameter param) {
            ArrayList<Parameter> params = mParamsMap.get(param.name);
            if (params == null) {
                params = new ArrayList<Parameter>();
                mParamsMap.put(param.name, params);
            }
            params.add(param);
        }

        /**
         * Returns the set of parameter names for this property.
         * @return The set of parameter names for this property.
         */
        public Set<String> getParameterNames() {
            return mParamsMap.keySet();
        }

        /**
         * Returns the list of parameters with the specified name.  May return
         * null if there are no such parameters.
         * @param name The name of the parameters that should be returned.
         * @return The list of parameters with the specified name.
         */
        public List<Parameter> getParameters(String name) {
            return mParamsMap.get(name);
        }

        /**
         * Returns the first parameter with the specified name.  May return
         * nll if there is no such parameter.
         * @param name The name of the parameter that should be returned.
         * @return The first parameter with the specified name.
         */
        public Parameter getFirstParameter(String name) {
            ArrayList<Parameter> params = mParamsMap.get(name);
            if (params == null || params.size() == 0) {
                return null;
            }
            return params.get(0);
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            toString(sb);
            return sb.toString();
        }

        /**
         * Helper method that appends this property to a StringBuilder.  The
         * caller is responsible for appending a newline after this property.
         */
        public void toString(StringBuilder sb) {
            sb.append(mName);
            Set<String> parameterNames = getParameterNames();
            for (String parameterName : parameterNames) {
                for (Parameter param : getParameters(parameterName)) {
                    sb.append(";");
                    param.toString(sb);
                }
            }
            sb.append(":");
            sb.append(mValue);
        }
    }

    /**
     * A parameter defined for an iCalendar property.
     */
    // TODO: make this a proper class rather than a struct?
    public static class Parameter {
        public String name;
        public String value;

        /**
         * Creates a new empty parameter.
         */
        public Parameter() {
        }

        /**
         * Creates a new parameter with the specified name and value.
         * @param name The name of the parameter.
         * @param value The value of the parameter.
         */
        public Parameter(String name, String value) {
            this.name = name;
            this.value = value;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            toString(sb);
            return sb.toString();
        }

        /**
         * Helper method that appends this parameter to a StringBuilder.
         */
        public void toString(StringBuilder sb) {
            sb.append(name);
            sb.append("=");
            sb.append(value);
        }
    }

    private static final class ParserState {
        // public int lineNumber = 0;
        public String line; // TODO: just point to original text
        public int index;
    }

    // use factory method
    private ICalendar() {
    }

    // TODO: get rid of this -- handle all of the parsing in one pass through
    // the text.
    private static String normalizeText(String text) {
        // it's supposed to be \r\n, but not everyone does that
        text = text.replaceAll("\r\n", "\n");
        text = text.replaceAll("\r", "\n");

        // we deal with line folding, by replacing all "\n " strings
        // with nothing.  The RFC specifies "\r\n " to be folded, but
        // we handle "\n " and "\r " too because we can get those.
        text = text.replaceAll("\n ", "");

        return text;
    }

    /**
     * Parses text into an iCalendar component.  Parses into the provided
     * component, if not null, or parses into a new component.  In the latter
     * case, expects a BEGIN as the first line.  Returns the provided or newly
     * created top-level component.
     */
    // TODO: use an index into the text, so we can make this a recursive
    // function?
    private static Component parseComponentImpl(Component component,
                                                String text)
            throws FormatException {
        Component current = component;
        ParserState state = new ParserState();
        state.index = 0;

        // split into lines
        String[] lines = text.split("\n");

        // each line is of the format:
        // name *(";" param) ":" value
        for (String line : lines) {
            try {
                current = parseLine(line, state, current);
                // if the provided component was null, we will return the root
                // NOTE: in this case, if the first line is not a BEGIN, a
                // FormatException will get thrown.   
                if (component == null) {
                    component = current;
                }
            } catch (FormatException fe) {
                if (Config.LOGV) {
                    Log.v(TAG, "Cannot parse " + line, fe);
                }
                // for now, we ignore the parse error.  Google Calendar seems
                // to be emitting some misformatted iCalendar objects.
            }
            continue;
        }
        return component;
    }

    /**
     * Parses a line into the provided component.  Creates a new component if
     * the line is a BEGIN, adding the newly created component to the provided
     * parent.  Returns whatever component is the current one (to which new
     * properties will be added) in the parse.
     */
    private static Component parseLine(String line, ParserState state,
                                       Component component)
            throws FormatException {
        state.line = line;
        int len = state.line.length();

        // grab the name
        char c = 0;
        for (state.index = 0; state.index < len; ++state.index) {
            c = line.charAt(state.index);
            if (c == ';' || c == ':') {
                break;
            }
        }
        String name = line.substring(0, state.index);

        if (component == null) {
            if (!Component.BEGIN.equals(name)) {
                throw new FormatException("Expected BEGIN");
            }
        }

        Property property;
        if (Component.BEGIN.equals(name)) {
            // start a new component
            String componentName = extractValue(state);
            Component child = new Component(componentName, component);
            if (component != null) {
                component.addChild(child);
            }
            return child;
        } else if (Component.END.equals(name)) {
            // finish the current component
            String componentName = extractValue(state);
            if (component == null ||
                    !componentName.equals(component.getName())) {
                throw new FormatException("Unexpected END " + componentName);
            }
            return component.getParent();
        } else {
            property = new Property(name);
        }

        if (c == ';') {
            Parameter parameter = null;
            while ((parameter = extractParameter(state)) != null) {
                property.addParameter(parameter);
            }
        }
        String value = extractValue(state);
        property.setValue(value);
        component.addProperty(property);
        return component;
    }

    /**
     * Extracts the value ":..." on the current line.  The first character must
     * be a ':'.
     */
    private static String extractValue(ParserState state)
            throws FormatException {
        String line = state.line;
        if (state.index >= line.length() || line.charAt(state.index) != ':') {
            throw new FormatException("Expected ':' before end of line in "
                    + line);
        }
        String value = line.substring(state.index + 1);
        state.index = line.length() - 1;
        return value;
    }

    /**
     * Extracts the next parameter from the line, if any.  If there are no more
     * parameters, returns null.
     */
    private static Parameter extractParameter(ParserState state)
            throws FormatException {
        String text = state.line;
        int len = text.length();
        Parameter parameter = null;
        int startIndex = -1;
        int equalIndex = -1;
        while (state.index < len) {
            char c = text.charAt(state.index);
            if (c == ':') {
                if (parameter != null) {
                    if (equalIndex == -1) {
                        throw new FormatException("Expected '=' within "
                                + "parameter in " + text);
                    }
                    parameter.value = text.substring(equalIndex + 1,
                                                     state.index);
                }
                return parameter; // may be null
            } else if (c == ';') {
                if (parameter != null) {
                    if (equalIndex == -1) {
                        throw new FormatException("Expected '=' within "
                                + "parameter in " + text);
                    }
                    parameter.value = text.substring(equalIndex + 1,
                                                     state.index);
                    return parameter;
                } else {
                    parameter = new Parameter();
                    startIndex = state.index;
                }
            } else if (c == '=') {
                equalIndex = state.index;
                if ((parameter == null) || (startIndex == -1)) {
                    throw new FormatException("Expected ';' before '=' in "
                            + text);
                }
                parameter.name = text.substring(startIndex + 1, equalIndex);
            }
            ++state.index;
        }
        throw new FormatException("Expected ':' before end of line in " + text);
    }

    /**
     * Parses the provided text into an iCalendar object.  The top-level
     * component must be of type VCALENDAR.
     * @param text The text to be parsed.
     * @return The top-level VCALENDAR component.
     * @throws FormatException Thrown if the text could not be parsed into an
     * iCalendar VCALENDAR object.
     */
    public static Component parseCalendar(String text) throws FormatException {
        Component calendar = parseComponent(null, text);
        if (calendar == null || !Component.VCALENDAR.equals(calendar.getName())) {
            throw new FormatException("Expected " + Component.VCALENDAR);
        }
        return calendar;
    }

    /**
     * Parses the provided text into an iCalendar event.  The top-level
     * component must be of type VEVENT.
     * @param text The text to be parsed.
     * @return The top-level VEVENT component.
     * @throws FormatException Thrown if the text could not be parsed into an
     * iCalendar VEVENT.
     */
    public static Component parseEvent(String text) throws FormatException {
        Component event = parseComponent(null, text);
        if (event == null || !Component.VEVENT.equals(event.getName())) {
            throw new FormatException("Expected " + Component.VEVENT);
        }
        return event;
    }

    /**
     * Parses the provided text into an iCalendar component.
     * @param text The text to be parsed.
     * @return The top-level component.
     * @throws FormatException Thrown if the text could not be parsed into an
     * iCalendar component.
     */
    public static Component parseComponent(String text) throws FormatException {
        return parseComponent(null, text);
    }

    /**
     * Parses the provided text, adding to the provided component.
     * @param component The component to which the parsed iCalendar data should
     * be added.
     * @param text The text to be parsed.
     * @return The top-level component.
     * @throws FormatException Thrown if the text could not be parsed as an
     * iCalendar object.
     */
    public static Component parseComponent(Component component, String text)
        throws FormatException {
        text = normalizeText(text);
        return parseComponentImpl(component, text);
    }
}

Other Android examples (source code examples)

Here is a short list of links related to this Android ICalendar.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.