/*
*
*
* 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();
}
}
}
|