FileDocCategorySizeDatePackage
FormatSupport.javaAPI DocphoneME MR2 API (J2ME)15624Wed May 02 18:00:28 BST 2007com.sun.kvem.midp.pim.formats

FormatSupport.java

/*
 *   
 *
 * Copyright  1990-2007 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */

package com.sun.kvem.midp.pim.formats;

import com.sun.kvem.midp.pim.UnsupportedPIMFormatException;
import java.io.UnsupportedEncodingException;
import java.util.Vector;

/**
 * Supporting methods for interpreting vCard and vCalendar encodings.
 *
 */
public class FormatSupport {

    /** Code name of the Quoted-Printable binary encoding. */
    public static final String QUOTED_PRINTABLE = "QUOTED_PRINTABLE";

    /** Code name of the Base64 binary encoding. */
    public static final String BASE64 = "BASE64";

    /** Code name of plain text binary encoding. */
    public static final String PLAIN_TEXT = "PLAIN_TEXT";

    /** Name of default character encoding. */
    public static final String UTF8 = "UTF-8";

    /** Repeat rule daily frequency char representation. */
    public static final char DAILY = 'D';

    /** Repeat rule weekly frequency char representation. */
    public static final char WEEKLY = 'W';

    /** Repeat rule monthly frequency char representation. */
    public static final char MONTHLY = 'M';

    /** Repeat rule yearly frequency char representation. */
    public static final char YEARLY = 'Y';

    /** Repeat rule day-in-month char representation. */
    public static final char DAY_IN_MONTH = 'D';

    /** Repeat rule week-in-month char representation. */
    public static final char WEEK_IN_MONTH = 'P';

    /** Repeat rule day-in-year char representation. */
    public static final char DAY_IN_YEAR = 'D';

    /** Repeat rule month-in-year char representation. */
    public static final char MONTH_IN_YEAR = 'M';

    /**
     * Gets the character set specified by the given property attributes.
     * The default is UTF-8, unless the attributes contain a CHARSET= entry.
     * @param attributes an array of vCard or vCalendar property attributes
     * @return the encoding specified by the attributes
     */
    public static String getCharSet(String[] attributes) {
        String charset = getAttributeValue(attributes, "CHARSET=", UTF8);
        try {
            "".getBytes(charset);
            return charset;
        } catch (UnsupportedEncodingException e) {
            // cannot use this encoding.
            return UTF8;
        }
    }

    /**
     * Gets an attribute of the form (key)(value), if one exists in the supplied
     * attributes list.
     * @param attributes an array of attributes
     * @param key the attribute key (e.g. "CHARSET=")
     * @param defaultValue a default value to be returned if no matching
     * attribute is found.
     * @return the value of the requested attribute, or defaultValue if the
     * attribute is not present.
     */
    public static String getAttributeValue(String[] attributes,
        String key, String defaultValue) {

        for (int i = 0; i < attributes.length; i++) {
            if (attributes[i].startsWith(key)) {
                return attributes[i].substring(key.length());
            }
        }
        return defaultValue;
    }

    /**
     * Gets the encoding used for a value with the given attributes.
     *
     * @param attributes an array of attributes
     * @return either VCardSupport.QUOTED_PRINTABLE, VCardSupport.BASE64
     * or VCardSupport.PLAIN_TEXT
     */
    public static String getEncoding(String[] attributes) {
        for (int i = 0; i < attributes.length; i++) {
            String s = attributes[i].toUpperCase();
            if (s.equals("ENCODING=QUOTED-PRINTABLE")
            || s.equals("QUOTED-PRINTABLE")) {
                return QUOTED_PRINTABLE;
            }
            if (s.equals("ENCODING=BASE64")
            || s.equals("BASE64")
            || s.equals("ENCODING=B")) {
                return BASE64;
            }
        }
        return PLAIN_TEXT;
    }

    /**
     * Converts a string from the given UTF-8 plain text encoding to the
     * specified encoding.
     * @param data input data to be converted
     * @param encoding input data encoding
     * @param charset output encoding
     * @return encoded string
     */
    public static String convertString(String data, String encoding,
            String charset) {
        if (encoding.equals(QUOTED_PRINTABLE)) {
            byte[] b = QuotedPrintableEncoding.fromQuotedPrintable(data);
            try {
                return new String(b, charset);
            } catch (UnsupportedEncodingException e) {
                // should not happen if charset was returned from getCharSet()
                return new String(b);
            }
        } else if (encoding.equals(BASE64)) {
            byte[] b = Base64Encoding.fromBase64(data);
            try {
                return new String(b, charset);
            } catch (UnsupportedEncodingException e) {
                // should not happen if charset was returned from getCharSet()
                return new String(b);
            }
        } else if (charset.equals(UTF8)) {
            return data;
        } else {
            try {
                return new String(data.getBytes(UTF8), charset);
            } catch (UnsupportedEncodingException e) {
                throw new Error(UTF8 + " encoding not available");
            }
        }
    }

    /**
     * Parses a separated list of strings into a string array.
     * An escaped separator (backslash followed by separatorChar) is not
     * treated as a separator.
     *
     * @param data input list to be parsed
     * @param separatorChar the character used to separate items
     * @param startingPoint Only use the part of the string that
     *     follows this index
     * @param skipFirstIfEmpty whether the first element should be skiped
     *     if it's empty (data starts with the separator).
     *     This flag is used to support empty category name
     * @return a non-null string array containing string elements
     */
    public static String[] split(String data, char separatorChar,
            int startingPoint, boolean skipFirstIfEmpty) {
        if (startingPoint == data.length()) {
            return new String[0];
        }

        // support for empty category name:
        // if data starts with separator, just skip it
        if (skipFirstIfEmpty && data.startsWith("" + separatorChar)) {
            startingPoint++;
        }

        // tokenize elements
        Vector elementList = new Vector();
        int startSearchAt = startingPoint;
        int startOfElement = startingPoint;
        for (int i; (i = data.indexOf(separatorChar, startSearchAt)) != -1; ) {
            if (i != 0 && data.charAt(i - 1) == '\\') {
                // escaped separator. don't treat it as a real separator
                startSearchAt = i + 1;
            } else {
                String element = data.substring(startOfElement, i);
                elementList.addElement(element);
                startSearchAt = startOfElement = i + 1;
            }
        }

        // there is no separator found
        if (elementList.size() == 0) {
            return new String[] { data.substring(startOfElement) };
        }

        // add the last element
        elementList.addElement(data.substring(startOfElement));

        // convert Vector to array
        int size = elementList.size();
        String[] elements = new String[size];
        for (int i = 0; i < size; i++) {
            elements[i] = (String) elementList.elementAt(i);
        }

        return elements;
    }

    /**
     * Parses a separated list of strings into a string array.
     * An escaped separator (backslash followed by separatorChar) is not
     * treated as a separator.
     *
     * @param data input list to be parsed
     * @param separatorChar the character used to separate items
     * @param startingPoint Only use the part of the string that
     *     follows this index
     * @return a non-null string array containing string elements
     */
    public static String[] split(String data, char separatorChar,
            int startingPoint) {
        return split(data, separatorChar, startingPoint, true);
    }

    /**
     * Joins the elements of a string array together into a single string.
     *
     * @param elements the string array
     * @param separator the string to be included between each pair of
     * successive elements
     * @return a string containing, alternately, elements of the string array
     * and the separator string
     */
    public static String join(String[] elements, String separator) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < elements.length; i++) {
            if (i > 0) {
                sb.append(separator);
            }
            sb.append(elements[i]);
        }
        return sb.toString();
    }

    /**
     * Sorts an array of integers.
     * @param a the list of integers
     */
    public static void sort(int[] a) {
        // insertion sort
        for (int j = 1; j < a.length; j++) {
            int v = a[j];
            int i = j - 1;
            while (i >= 0 && a[i] > v) {
                a[i + 1] = a[i];
                i--;
            }
            a[i + 1] = v;
        }
    }

    /**
     * Checks to see if a sorted array of integers contains a given integer.
     * @param a input array to be checked
     * @param value to be checked int the array
     * @return <code>true</code> if the value is found int the array
     */
    public static boolean contains(int[] a, int value) {
        // binary chop search
        int lowerBound = 0;
        int upperBound = a.length - 1;
        while (upperBound - lowerBound >= 0) {
            int i = lowerBound + (upperBound - lowerBound) / 2;
            int v = a[i];
            if (v > value) {
                // look between lowerBound and i
                upperBound = i - 1;
            } else if (v < value) {
                // look between i and upperBound
                lowerBound = i + 1;
            } else {
                return true;
            }
        }
        return false;
    }
    /**
     * Handles data element parsing for V-object inputs.
     */
    public static class DataElement {
        /** Name of the property. */
        String propertyName;
        /** Attributes of the element. */
        String[] attributes;
        /** Data to be processed. */
        String data;
    }

    /**
     * Extracts data from a vCard or vCalendar line.
     *
     * @param line the input line, in the form
     * (propertyname)[;(attributes)]:(data)
     * @return the property data
     * @throws UnsupportedPIMFormatException if the line is not in the expected
     * format
     */
    public static DataElement parseObjectLine(String line)
        throws UnsupportedPIMFormatException {

        // break the line into property name, attributes and data
        int i = line.indexOf(':');
        if (i == -1 || i == 0) {
            // every line in a vCalendar object must have a colon delimiter
            throw new UnsupportedPIMFormatException(
            "Invalid line: '" + line + "'");
        }
        DataElement element = new DataElement();
        element.data = line.substring(i + 1).trim();
        String prefix = line.substring(0, i).trim();
        i = prefix.indexOf(';');
        if (i == -1) {
            element.propertyName = prefix.toUpperCase();
            element.attributes = new String[0];
        } else {
            element.propertyName = prefix.substring(0, i).toUpperCase();
            element.attributes = FormatSupport.split(prefix, ';', i + 1);
            for (int j = 0; j < element.attributes.length; j++) {
                element.attributes[j] = element.attributes[j].toUpperCase();
            }
        }
        // propertyName could contain a group name. (e.g. HOME.FN:)
        // we don't have to do anything with the group name - there is
        // really nothing to do with it - but we do have to process it.
        // remove a group name, if one exists:
        i = element.propertyName.lastIndexOf('.');
        if (i != -1) {
            element.propertyName = element.propertyName.substring(i + 1);
        }
        return element;
    }

    /**
     * Interpret a vCard or vCalendar data element as a string, taking
     * into account any encoding parameters specified in the attribute array.
     * @param attributes An array of attributes obtained from a class to
     * parseObjectLine.
     * @param data The string data of a vCard or vCalendar object line,
     * obtained from a call to parseObjectLine.
     * @return the decoded string data
     */
    public static String parseString(String[] attributes, String data) {
        String charset = getCharSet(attributes);
        String encoding = getEncoding(attributes);
        return convertString(data, encoding, charset);
    }

    /**
     * Interpret a vCard or vCalendar data element as a string array, taking
     * into account any encoding parameters specified in the attribute array.
     * @param attributes An array of attributes obtained from a call to
     * parseObjectLine.
     * @param data The string data of a vCard or vCalendar object line,
     * obtained from a call to parseObjectLine.
     * @return the decoded string array data
     */
    public static String[] parseStringArray(String[] attributes, String data) {
        String charset = getCharSet(attributes);
        String encoding = getEncoding(attributes);
        String[] elements = split(data, ';', 0, false);
        for (int i = 0; i < elements.length; i++) {
            elements[i] = convertString(elements[i], encoding, charset);
            // treat empty elements as null
            if ("".equals(elements[i])) {
                elements[i] = null;
            }
        }
        return elements;
    }

    /**
     * Interpret a vCard or vCalendar data element as a byte array, taking
     * into account any encoding parameters specified in the attribute array.
     * @param attributes An array of attributes obtained from a class to
     * parseObjectLine.
     * @param data The string data of a vCard or vCalendar object line,
     * obtained from a call to parseObjectLine.
     * @return the decoded binary data
     */
    public static byte[] parseBinary(String[] attributes, String data) {
        String encoding = getEncoding(attributes);
        if (encoding.equals(QUOTED_PRINTABLE)) {
            return QuotedPrintableEncoding.fromQuotedPrintable(data);
        } else if (encoding.equals(BASE64)) {
            return Base64Encoding.fromBase64(data);
        } else {
            return data.getBytes();
        }
    }

}