FileDocCategorySizeDatePackage
VCalParser_V20.javaAPI DocAndroid 1.5 API7018Wed May 06 22:41:56 BST 2009android.syncml.pim.vcalendar

VCalParser_V20.java

/*
 * 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.syncml.pim.vcalendar;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashSet;

import android.syncml.pim.VBuilder;

public class VCalParser_V20 extends VCalParser_V10 {
    private static final String V10LINEBREAKER = "\r\n";

    private static final HashSet<String> acceptableComponents = new HashSet<String>(
            Arrays.asList("VEVENT", "VTODO", "VALARM", "VTIMEZONE"));

    private static final HashSet<String> acceptableV20Props = new HashSet<String>(
            Arrays.asList("DESCRIPTION", "DTEND", "DTSTART", "DUE",
                    "COMPLETED", "RRULE", "STATUS", "SUMMARY", "LOCATION"));

    private boolean hasTZ = false; // MUST only have one TZ property

    private String[] lines;

    private int index;

    @Override
    public boolean parse(InputStream is, String encoding, VBuilder builder)
            throws IOException {
        // get useful info for android calendar, and alter to vcal 1.0
        byte[] bytes = new byte[is.available()];
        is.read(bytes);
        String scStr = new String(bytes);
        StringBuilder v10str = new StringBuilder("");

        lines = splitProperty(scStr);
        index = 0;

        if ("BEGIN:VCALENDAR".equals(lines[index]))
            v10str.append("BEGIN:VCALENDAR" + V10LINEBREAKER);
        else
            return false;
        index++;
        if (false == parseV20Calbody(lines, v10str)
                || index > lines.length - 1)
            return false;

        if (lines.length - 1 == index && "END:VCALENDAR".equals(lines[index]))
            v10str.append("END:VCALENDAR" + V10LINEBREAKER);
        else
            return false;

        return super.parse(
                // use vCal 1.0 parser
                new ByteArrayInputStream(v10str.toString().getBytes()),
                encoding, builder);
    }

    /**
     * Parse and pick acceptable iCalendar body and translate it to
     * calendarV1.0 format.
     * @param lines iCalendar components/properties line list.
     * @param buffer calendarV10 format string buffer
     * @return true for success, or false
     */
    private boolean parseV20Calbody(String[] lines, StringBuilder buffer) {
        try {
            while (!"VERSION:2.0".equals(lines[index]))
                index++;
            buffer.append("VERSION:1.0" + V10LINEBREAKER);

            index++;
            for (; index < lines.length - 1; index++) {
                String[] keyAndValue = lines[index].split(":", 2);
                String key = keyAndValue[0];
                String value = keyAndValue[1];

                if ("BEGIN".equals(key.trim())) {
                    if (!key.equals(key.trim()))
                        return false; // MUST be "BEGIN:componentname"
                    index++;
                    if (false == parseV20Component(value, buffer))
                        return false;
                }
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            return false;
        }

        return true;
    }

    /**
     * Parse and pick acceptable calendar V2.0's component and translate it to
     * V1.0 format.
     * @param compName component name
     * @param buffer calendarV10 format string buffer
     * @return true for success, or false
     * @throws ArrayIndexOutOfBoundsException
     */
    private boolean parseV20Component(String compName, StringBuilder buffer)
            throws ArrayIndexOutOfBoundsException {
        String endTag = "END:" + compName;
        String[] propAndValue;
        String propName, value;

        if (acceptableComponents.contains(compName)) {
            if ("VEVENT".equals(compName) || "VTODO".equals(compName)) {
                buffer.append("BEGIN:" + compName + V10LINEBREAKER);
                while (!endTag.equals(lines[index])) {
                    propAndValue = lines[index].split(":", 2);
                    propName = propAndValue[0].split(";", 2)[0];
                    value = propAndValue[1];

                    if ("".equals(lines[index]))
                        buffer.append(V10LINEBREAKER);
                    else if (acceptableV20Props.contains(propName)) {
                        buffer.append(propName + ":" + value + V10LINEBREAKER);
                    } else if ("BEGIN".equals(propName.trim())) {
                        // MUST be BEGIN:VALARM
                        if (propName.equals(propName.trim())
                                && "VALARM".equals(value)) {
                            buffer.append("AALARM:default" + V10LINEBREAKER);
                            while (!"END:VALARM".equals(lines[index]))
                                index++;
                        } else
                            return false;
                    }
                    index++;
                } // end while
                buffer.append(endTag + V10LINEBREAKER);
            } else if ("VALARM".equals(compName)) { // VALARM component MUST
                // only appear within either VEVENT or VTODO
                return false;
            } else if ("VTIMEZONE".equals(compName)) {
                do {
                    if (false == hasTZ) {// MUST only have 1 time TZ property
                        propAndValue = lines[index].split(":", 2);
                        propName = propAndValue[0].split(";", 2)[0];

                        if ("TZOFFSETFROM".equals(propName)) {
                            value = propAndValue[1];
                            buffer.append("TZ" + ":" + value + V10LINEBREAKER);
                            hasTZ = true;
                        }
                    }
                    index++;
                } while (!endTag.equals(lines[index]));
            } else
                return false;
        } else {
            while (!endTag.equals(lines[index]))
                index++;
        }

        return true;
    }

    /** split ever property line to String[], not split folding line. */
    private String[] splitProperty(String scStr) {
        /*
         * Property splitted by \n, and unfold folding lines by removing
         * CRLF+LWSP-char
         */
        scStr = scStr.replaceAll("\r\n", "\n").replaceAll("\n ", "")
                .replaceAll("\n\t", "");
        String[] strs = scStr.split("\n");
        return strs;
    }
}