001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package com.sun.kvem.midp.pim.formats;
028:
029: import com.sun.kvem.midp.pim.UnsupportedPIMFormatException;
030: import java.io.UnsupportedEncodingException;
031: import java.util.Vector;
032:
033: /**
034: * Supporting methods for interpreting vCard and vCalendar encodings.
035: *
036: */
037: public class FormatSupport {
038:
039: /** Code name of the Quoted-Printable binary encoding. */
040: public static final String QUOTED_PRINTABLE = "QUOTED_PRINTABLE";
041:
042: /** Code name of the Base64 binary encoding. */
043: public static final String BASE64 = "BASE64";
044:
045: /** Code name of plain text binary encoding. */
046: public static final String PLAIN_TEXT = "PLAIN_TEXT";
047:
048: /** Name of default character encoding. */
049: public static final String UTF8 = "UTF-8";
050:
051: /** Repeat rule daily frequency char representation. */
052: public static final char DAILY = 'D';
053:
054: /** Repeat rule weekly frequency char representation. */
055: public static final char WEEKLY = 'W';
056:
057: /** Repeat rule monthly frequency char representation. */
058: public static final char MONTHLY = 'M';
059:
060: /** Repeat rule yearly frequency char representation. */
061: public static final char YEARLY = 'Y';
062:
063: /** Repeat rule day-in-month char representation. */
064: public static final char DAY_IN_MONTH = 'D';
065:
066: /** Repeat rule week-in-month char representation. */
067: public static final char WEEK_IN_MONTH = 'P';
068:
069: /** Repeat rule day-in-year char representation. */
070: public static final char DAY_IN_YEAR = 'D';
071:
072: /** Repeat rule month-in-year char representation. */
073: public static final char MONTH_IN_YEAR = 'M';
074:
075: /**
076: * Gets the character set specified by the given property attributes.
077: * The default is UTF-8, unless the attributes contain a CHARSET= entry.
078: * @param attributes an array of vCard or vCalendar property attributes
079: * @return the encoding specified by the attributes
080: */
081: public static String getCharSet(String[] attributes) {
082: String charset = getAttributeValue(attributes, "CHARSET=", UTF8);
083: try {
084: "".getBytes(charset);
085: return charset;
086: } catch (UnsupportedEncodingException e) {
087: // cannot use this encoding.
088: return UTF8;
089: }
090: }
091:
092: /**
093: * Gets an attribute of the form (key)(value), if one exists in the supplied
094: * attributes list.
095: * @param attributes an array of attributes
096: * @param key the attribute key (e.g. "CHARSET=")
097: * @param defaultValue a default value to be returned if no matching
098: * attribute is found.
099: * @return the value of the requested attribute, or defaultValue if the
100: * attribute is not present.
101: */
102: public static String getAttributeValue(String[] attributes,
103: String key, String defaultValue) {
104:
105: for (int i = 0; i < attributes.length; i++) {
106: if (attributes[i].startsWith(key)) {
107: return attributes[i].substring(key.length());
108: }
109: }
110: return defaultValue;
111: }
112:
113: /**
114: * Gets the encoding used for a value with the given attributes.
115: *
116: * @param attributes an array of attributes
117: * @return either VCardSupport.QUOTED_PRINTABLE, VCardSupport.BASE64
118: * or VCardSupport.PLAIN_TEXT
119: */
120: public static String getEncoding(String[] attributes) {
121: for (int i = 0; i < attributes.length; i++) {
122: String s = attributes[i].toUpperCase();
123: if (s.equals("ENCODING=QUOTED-PRINTABLE")
124: || s.equals("QUOTED-PRINTABLE")) {
125: return QUOTED_PRINTABLE;
126: }
127: if (s.equals("ENCODING=BASE64") || s.equals("BASE64")
128: || s.equals("ENCODING=B")) {
129: return BASE64;
130: }
131: }
132: return PLAIN_TEXT;
133: }
134:
135: /**
136: * Converts a string from the given UTF-8 plain text encoding to the
137: * specified encoding.
138: * @param data input data to be converted
139: * @param encoding input data encoding
140: * @param charset output encoding
141: * @return encoded string
142: */
143: public static String convertString(String data, String encoding,
144: String charset) {
145: if (encoding.equals(QUOTED_PRINTABLE)) {
146: byte[] b = QuotedPrintableEncoding
147: .fromQuotedPrintable(data);
148: try {
149: return new String(b, charset);
150: } catch (UnsupportedEncodingException e) {
151: // should not happen if charset was returned from getCharSet()
152: return new String(b);
153: }
154: } else if (encoding.equals(BASE64)) {
155: byte[] b = Base64Encoding.fromBase64(data);
156: try {
157: return new String(b, charset);
158: } catch (UnsupportedEncodingException e) {
159: // should not happen if charset was returned from getCharSet()
160: return new String(b);
161: }
162: } else if (charset.equals(UTF8)) {
163: return data;
164: } else {
165: try {
166: return new String(data.getBytes(UTF8), charset);
167: } catch (UnsupportedEncodingException e) {
168: throw new Error(UTF8 + " encoding not available");
169: }
170: }
171: }
172:
173: /**
174: * Parses a separated list of strings into a string array.
175: * An escaped separator (backslash followed by separatorChar) is not
176: * treated as a separator.
177: *
178: * @param data input list to be parsed
179: * @param separatorChar the character used to separate items
180: * @param startingPoint Only use the part of the string that
181: * follows this index
182: * @param skipFirstIfEmpty whether the first element should be skiped
183: * if it's empty (data starts with the separator).
184: * This flag is used to support empty category name
185: * @return a non-null string array containing string elements
186: */
187: public static String[] split(String data, char separatorChar,
188: int startingPoint, boolean skipFirstIfEmpty) {
189: if (startingPoint == data.length()) {
190: return new String[0];
191: }
192:
193: // support for empty category name:
194: // if data starts with separator, just skip it
195: if (skipFirstIfEmpty && data.startsWith("" + separatorChar)) {
196: startingPoint++;
197: }
198:
199: // tokenize elements
200: Vector elementList = new Vector();
201: int startSearchAt = startingPoint;
202: int startOfElement = startingPoint;
203: for (int i; (i = data.indexOf(separatorChar, startSearchAt)) != -1;) {
204: if (i != 0 && data.charAt(i - 1) == '\\') {
205: // escaped separator. don't treat it as a real separator
206: startSearchAt = i + 1;
207: } else {
208: String element = data.substring(startOfElement, i);
209: elementList.addElement(element);
210: startSearchAt = startOfElement = i + 1;
211: }
212: }
213:
214: // there is no separator found
215: if (elementList.size() == 0) {
216: return new String[] { data.substring(startOfElement) };
217: }
218:
219: // add the last element
220: elementList.addElement(data.substring(startOfElement));
221:
222: // convert Vector to array
223: int size = elementList.size();
224: String[] elements = new String[size];
225: for (int i = 0; i < size; i++) {
226: elements[i] = (String) elementList.elementAt(i);
227: }
228:
229: return elements;
230: }
231:
232: /**
233: * Parses a separated list of strings into a string array.
234: * An escaped separator (backslash followed by separatorChar) is not
235: * treated as a separator.
236: *
237: * @param data input list to be parsed
238: * @param separatorChar the character used to separate items
239: * @param startingPoint Only use the part of the string that
240: * follows this index
241: * @return a non-null string array containing string elements
242: */
243: public static String[] split(String data, char separatorChar,
244: int startingPoint) {
245: return split(data, separatorChar, startingPoint, true);
246: }
247:
248: /**
249: * Joins the elements of a string array together into a single string.
250: *
251: * @param elements the string array
252: * @param separator the string to be included between each pair of
253: * successive elements
254: * @return a string containing, alternately, elements of the string array
255: * and the separator string
256: */
257: public static String join(String[] elements, String separator) {
258: StringBuffer sb = new StringBuffer();
259: for (int i = 0; i < elements.length; i++) {
260: if (i > 0) {
261: sb.append(separator);
262: }
263: sb.append(elements[i]);
264: }
265: return sb.toString();
266: }
267:
268: /**
269: * Sorts an array of integers.
270: * @param a the list of integers
271: */
272: public static void sort(int[] a) {
273: // insertion sort
274: for (int j = 1; j < a.length; j++) {
275: int v = a[j];
276: int i = j - 1;
277: while (i >= 0 && a[i] > v) {
278: a[i + 1] = a[i];
279: i--;
280: }
281: a[i + 1] = v;
282: }
283: }
284:
285: /**
286: * Checks to see if a sorted array of integers contains a given integer.
287: * @param a input array to be checked
288: * @param value to be checked int the array
289: * @return <code>true</code> if the value is found int the array
290: */
291: public static boolean contains(int[] a, int value) {
292: // binary chop search
293: int lowerBound = 0;
294: int upperBound = a.length - 1;
295: while (upperBound - lowerBound >= 0) {
296: int i = lowerBound + (upperBound - lowerBound) / 2;
297: int v = a[i];
298: if (v > value) {
299: // look between lowerBound and i
300: upperBound = i - 1;
301: } else if (v < value) {
302: // look between i and upperBound
303: lowerBound = i + 1;
304: } else {
305: return true;
306: }
307: }
308: return false;
309: }
310:
311: /**
312: * Handles data element parsing for V-object inputs.
313: */
314: public static class DataElement {
315: /** Name of the property. */
316: String propertyName;
317: /** Attributes of the element. */
318: String[] attributes;
319: /** Data to be processed. */
320: String data;
321: }
322:
323: /**
324: * Extracts data from a vCard or vCalendar line.
325: *
326: * @param line the input line, in the form
327: * (propertyname)[;(attributes)]:(data)
328: * @return the property data
329: * @throws UnsupportedPIMFormatException if the line is not in the expected
330: * format
331: */
332: public static DataElement parseObjectLine(String line)
333: throws UnsupportedPIMFormatException {
334:
335: // break the line into property name, attributes and data
336: int i = line.indexOf(':');
337: if (i == -1 || i == 0) {
338: // every line in a vCalendar object must have a colon delimiter
339: throw new UnsupportedPIMFormatException("Invalid line: '"
340: + line + "'");
341: }
342: DataElement element = new DataElement();
343: element.data = line.substring(i + 1).trim();
344: String prefix = line.substring(0, i).trim();
345: i = prefix.indexOf(';');
346: if (i == -1) {
347: element.propertyName = prefix.toUpperCase();
348: element.attributes = new String[0];
349: } else {
350: element.propertyName = prefix.substring(0, i).toUpperCase();
351: element.attributes = FormatSupport
352: .split(prefix, ';', i + 1);
353: for (int j = 0; j < element.attributes.length; j++) {
354: element.attributes[j] = element.attributes[j]
355: .toUpperCase();
356: }
357: }
358: // propertyName could contain a group name. (e.g. HOME.FN:)
359: // we don't have to do anything with the group name - there is
360: // really nothing to do with it - but we do have to process it.
361: // remove a group name, if one exists:
362: i = element.propertyName.lastIndexOf('.');
363: if (i != -1) {
364: element.propertyName = element.propertyName
365: .substring(i + 1);
366: }
367: return element;
368: }
369:
370: /**
371: * Interpret a vCard or vCalendar data element as a string, taking
372: * into account any encoding parameters specified in the attribute array.
373: * @param attributes An array of attributes obtained from a class to
374: * parseObjectLine.
375: * @param data The string data of a vCard or vCalendar object line,
376: * obtained from a call to parseObjectLine.
377: * @return the decoded string data
378: */
379: public static String parseString(String[] attributes, String data) {
380: String charset = getCharSet(attributes);
381: String encoding = getEncoding(attributes);
382: return convertString(data, encoding, charset);
383: }
384:
385: /**
386: * Interpret a vCard or vCalendar data element as a string array, taking
387: * into account any encoding parameters specified in the attribute array.
388: * @param attributes An array of attributes obtained from a call to
389: * parseObjectLine.
390: * @param data The string data of a vCard or vCalendar object line,
391: * obtained from a call to parseObjectLine.
392: * @return the decoded string array data
393: */
394: public static String[] parseStringArray(String[] attributes,
395: String data) {
396: String charset = getCharSet(attributes);
397: String encoding = getEncoding(attributes);
398: String[] elements = split(data, ';', 0, false);
399: for (int i = 0; i < elements.length; i++) {
400: elements[i] = convertString(elements[i], encoding, charset);
401: // treat empty elements as null
402: if ("".equals(elements[i])) {
403: elements[i] = null;
404: }
405: }
406: return elements;
407: }
408:
409: /**
410: * Interpret a vCard or vCalendar data element as a byte array, taking
411: * into account any encoding parameters specified in the attribute array.
412: * @param attributes An array of attributes obtained from a class to
413: * parseObjectLine.
414: * @param data The string data of a vCard or vCalendar object line,
415: * obtained from a call to parseObjectLine.
416: * @return the decoded binary data
417: */
418: public static byte[] parseBinary(String[] attributes, String data) {
419: String encoding = getEncoding(attributes);
420: if (encoding.equals(QUOTED_PRINTABLE)) {
421: return QuotedPrintableEncoding.fromQuotedPrintable(data);
422: } else if (encoding.equals(BASE64)) {
423: return Base64Encoding.fromBase64(data);
424: } else {
425: return data.getBytes();
426: }
427: }
428:
429: }
|