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.ContactImpl;
030: import com.sun.kvem.midp.pim.LineReader;
031: import com.sun.kvem.midp.pim.PIMFormat;
032: import com.sun.kvem.midp.pim.PIMHandler;
033: import com.sun.kvem.midp.pim.UnsupportedPIMFormatException;
034: import com.sun.kvem.midp.pim.AbstractPIMList;
035: import com.sun.kvem.midp.pim.AbstractPIMItem;
036: import java.io.IOException;
037: import java.io.InputStream;
038: import java.io.OutputStream;
039: import java.io.OutputStreamWriter;
040: import java.io.Writer;
041: import javax.microedition.pim.Contact;
042: import javax.microedition.pim.PIM;
043: import javax.microedition.pim.PIMException;
044: import javax.microedition.pim.PIMItem;
045: import javax.microedition.pim.PIMList;
046:
047: /**
048: * Partial implementation of PIMEncoding for VCard/2.1 and VCard/3.0.
049: *
050: */
051: public abstract class VCardFormat extends EndMatcher implements
052: PIMFormat {
053:
054: /**
055: * Returns the version number of vCard implemented.
056: * @return the VCard version number
057: */
058: protected abstract String getVersion();
059:
060: /**
061: * Gets the vCard property name used to store categories.
062: * @return the vCard category property name
063: */
064: protected abstract String getCategoryProperty();
065:
066: /**
067: * Gets the vCard property name used to store classes.
068: * @return the class property name
069: */
070: protected abstract String getClassProperty();
071:
072: /**
073: * Gets the binary value describing all flags in a vCard line.
074: * @param attributes fields to be parsed
075: * @return binary coded settings
076: */
077: protected abstract int parseAttributes(String[] attributes);
078:
079: /**
080: * Gets the name of the default binary encoding.
081: * This is "BASE64" for vCard 2.1 and "B" for vCard 3.0.
082: * @return the default binary encoding
083: */
084: protected abstract String getBinaryEncodingName();
085:
086: /**
087: * VCard formatting class.
088: */
089: public VCardFormat() {
090: super ("VCARD");
091: }
092:
093: /**
094: * Gets the code name of this encoding (e.g. "VCARD/2.1").
095: * @return the encoding name
096: */
097: public String getName() {
098: return "VCARD/" + getVersion();
099: }
100:
101: /**
102: * Checks to see if a given PIM list type is supported by this encoding.
103: * @param pimListType int representing the PIM list type to check
104: * @return true if the type can be read and written by this encoding,
105: * false otherwise
106: */
107: public boolean isTypeSupported(int pimListType) {
108: return pimListType == PIM.CONTACT_LIST;
109: }
110:
111: /**
112: * Serializes a PIMItem.
113: * @param out Stream to which serialized data is written
114: * @param encoding Character encoding to use for serialized data
115: * @param pimItem The item to write to the stream
116: * @throws IOException if an error occurs while writing
117: */
118: public void encode(OutputStream out, String encoding,
119: PIMItem pimItem) throws IOException {
120: Writer w = new OutputStreamWriter(out, encoding);
121: w.write("BEGIN:VCARD\r\n");
122: w.write("VERSION:");
123: w.write(getVersion());
124: w.write("\r\n");
125: // write all fields
126: int[] fields = pimItem.getFields();
127: FormatSupport.sort(fields);
128: for (int i = 0; i < fields.length; i++) {
129: int valueCount = pimItem.countValues(fields[i]);
130: for (int j = 0; j < valueCount; j++) {
131: writeValue(w, pimItem, fields[i], j);
132: }
133: }
134: // write categories.
135: String categories = FormatSupport.join(pimItem.getCategories(),
136: ",");
137: if (categories.length() > 0) {
138: w.write(getCategoryProperty());
139: w.write(":");
140: w.write(categories);
141: w.write("\r\n");
142: }
143: w.write("END:VCARD\r\n");
144: w.flush();
145: }
146:
147: /**
148: * Writes a single vCard line.
149: * @param w output stream target
150: * @param item the data to to written
151: * @param field the attribute to be processed
152: * @param index the offset of the data to be processed
153: * @throws IOException if an error occurs while writing
154: */
155: protected void writeValue(Writer w, PIMItem item, int field,
156: int index) throws IOException {
157:
158: String label = VCardSupport.getFieldLabel(field);
159: switch (field) {
160: case Contact.FORMATTED_NAME:
161: case Contact.FORMATTED_ADDR:
162: case Contact.PHOTO_URL:
163: case Contact.TEL:
164: case Contact.EMAIL:
165: case Contact.TITLE:
166: case Contact.ORG:
167: case Contact.NICKNAME:
168: case Contact.NOTE:
169: case Contact.UID:
170: case Contact.URL:
171: case Contact.PUBLIC_KEY_STRING: {
172: String sValue = item.getString(field, index);
173: if (sValue != null) {
174: w.write(label);
175: writeAttributes(w, item.getAttributes(field, index));
176: w.write(":");
177: w.write(sValue);
178: w.write("\r\n");
179: }
180: break;
181: }
182: case Contact.NAME:
183: case Contact.ADDR: {
184: String[] aValue = item.getStringArray(field, index);
185: if (aValue != null) {
186: w.write(label);
187: writeAttributes(w, item.getAttributes(field, index));
188: w.write(":");
189: writeStringArray(w, aValue);
190: w.write("\r\n");
191: }
192: break;
193: }
194: case Contact.PHOTO:
195: case Contact.PUBLIC_KEY: {
196: byte[] bValue = item.getBinary(field, index);
197: if (bValue != null) {
198: w.write(label);
199: w.write(";ENCODING=");
200: w.write(getBinaryEncodingName());
201: writeAttributes(w, item.getAttributes(field, index));
202: w.write(":\r\n ");
203: w.write(Base64Encoding.toBase64(bValue,
204: 76 /* line width */, 4 /* indent */));
205: w.write("\r\n");
206: }
207: break;
208: }
209: case Contact.BIRTHDAY:
210: case Contact.REVISION:
211: w.write(label);
212: writeAttributes(w, item.getAttributes(field, index));
213: w.write(":");
214: writeDate(w, item.getDate(field, index));
215: w.write("\r\n");
216: break;
217: case Contact.CLASS: {
218: int iValue = item.getInt(field, index);
219: String sValue = VCardSupport.getClassType(iValue);
220: if (sValue != null) {
221: w.write(getClassProperty());
222: writeAttributes(w, item.getAttributes(field, index));
223: w.write(":");
224: w.write(sValue);
225: w.write("\r\n");
226: }
227: break;
228: }
229: default:
230: // field cannot be written. ignore it.
231: }
232: }
233:
234: /**
235: * Writes a vCard field with multiple elements, such as ADR.
236: * @param w output stream target
237: * @param data the strings to write
238: * @throws IOException if an error occurs while writing
239: */
240: protected void writeStringArray(Writer w, String[] data)
241: throws IOException {
242: for (int i = 0; i < data.length; i++) {
243: if (data[i] != null) {
244: w.write(data[i]);
245: }
246: if (i != data.length - 1) {
247: w.write(';');
248: }
249: }
250: }
251:
252: /**
253: * Writes a vCard date field.
254: * @param w output stream target
255: * @param date data to be written
256: * @throws IOException if an error occurs while writing
257: */
258: protected void writeDate(Writer w, long date) throws IOException {
259: w.write(PIMHandler.getInstance().composeDate(date));
260: }
261:
262: /**
263: * Writes the attributes for a field.
264: * @param w output stream target
265: * @param attributes data to be written
266: * @throws IOException if an error occurs while writing
267: */
268: protected abstract void writeAttributes(Writer w, int attributes)
269: throws IOException;
270:
271: /**
272: * Constructs one or more PIMItems from serialized data.
273: * @param in Stream containing serialized data
274: * @param encoding Character encoding of the stream
275: * @param list PIMList to which items should be added, or null if the items
276: * should not be part of a list
277: * @throws UnsupportedPIMFormatException if the serialized data cannot be
278: * interpreted by this encoding.
279: * @return a non-empty array of PIMItems containing the objects described in
280: * the serialized data, or null if no items are available
281: * @throws IOException if an error occurs while reading
282: */
283: public PIMItem[] decode(InputStream in, String encoding,
284: PIMList list) throws IOException {
285:
286: LineReader r = new LineReader(in, encoding, this );
287: ContactImpl contact = decode(r, list);
288: if (contact == null) {
289: return null;
290: } else {
291: return new ContactImpl[] { contact };
292: }
293: }
294:
295: /**
296: * Constructs a single PIMItem from serialized data.
297: * @param in LineReader containing serialized data
298: * @param list PIM list to which the item belongs
299: * @throws UnsupportedPIMFormatException if the serialized data cannot be
300: * interpreted by this encoding.
301: * @return an unserialized Contact, or null if no data was available
302: */
303: private ContactImpl decode(LineReader in, PIMList list)
304: throws IOException {
305: String line = in.readLine();
306: if (line == null) {
307: return null;
308: }
309: if (!line.toUpperCase().equals("BEGIN:VCARD")) {
310: throw new UnsupportedPIMFormatException("Not a vCard :'"
311: + line + "'");
312: }
313: String categoryProperty = getCategoryProperty();
314: ContactImpl contact = new ContactImpl((AbstractPIMList) list);
315: while ((line = in.readLine()) != null) {
316: FormatSupport.DataElement element = FormatSupport
317: .parseObjectLine(line);
318: if (element.propertyName.equals("END")) {
319: return contact;
320: } else if (element.propertyName.equals("VERSION")) {
321: if (!element.data.equals(getVersion())) {
322: throw new UnsupportedPIMFormatException("Version "
323: + element.data + " not supported");
324: }
325: } else if (element.propertyName.equals(categoryProperty)) {
326: String[] categories = FormatSupport.split(element.data,
327: ',', 0);
328: for (int j = 0; j < categories.length; j++) {
329: try {
330: contact.addToCategory(categories[j]);
331: } catch (PIMException e) {
332: // cannot add to category
333: }
334: }
335: } else {
336: importData(contact, element.propertyName,
337: element.attributes, element.data, contact
338: .getPIMListHandle());
339: }
340: }
341: throw new IOException("Unterminated vCard");
342: }
343:
344: /**
345: * Parses a single vCard line.
346: * @param contact element to populate
347: * @param prefix filter for selecting fields
348: * @param attributes fields to be processed
349: * @param data input to be filtered
350: * @param listHandle handle of the list containing the contact being parsed
351: */
352: private void importData(Contact contact, String prefix,
353: String[] attributes, String data, Object listHandle) {
354:
355: int attr = parseAttributes(attributes);
356: int field = VCardSupport.getFieldCode(prefix);
357: if (field == -1 && prefix.equals(getClassProperty())) {
358: field = Contact.CLASS;
359: }
360: if (!PIMHandler.getInstance().isSupportedField(listHandle,
361: field)) {
362: return;
363: }
364: switch (field) {
365: case Contact.FORMATTED_NAME:
366: case Contact.FORMATTED_ADDR:
367: case Contact.TEL:
368: case Contact.EMAIL:
369: case Contact.TITLE:
370: case Contact.ORG:
371: case Contact.NICKNAME:
372: case Contact.NOTE:
373: case Contact.UID:
374: case Contact.URL: {
375: String sdata = FormatSupport.parseString(attributes, data);
376: contact.addString(field, attr, sdata);
377: break;
378: }
379: case Contact.NAME:
380: case Contact.ADDR: {
381: String[] elements = FormatSupport.parseStringArray(
382: attributes, data);
383: int elementCount = PIMHandler.getInstance()
384: .getStringArraySize(listHandle, field);
385: if (elements.length != elementCount) {
386: String[] a = new String[elementCount];
387: System.arraycopy(elements, 0, a, 0, Math.min(
388: elements.length, elementCount));
389: elements = a;
390: }
391: contact.addStringArray(field, attr, elements);
392: break;
393: }
394: case Contact.BIRTHDAY:
395: case Contact.REVISION: {
396: long date = PIMHandler.getInstance().parseDate(data);
397: contact.addDate(field, attr, date);
398: break;
399: }
400: case Contact.PHOTO: {
401: String valueType = FormatSupport.getAttributeValue(
402: attributes, "VALUE=", null);
403: if (valueType == null) {
404: // binary data
405: byte[] bdata = FormatSupport.parseBinary(attributes,
406: data);
407: if (bdata.length != 0) {
408: contact.addBinary(Contact.PHOTO, attr, bdata, 0,
409: bdata.length);
410: }
411: } else if (valueType.equals("URL")) {
412: String sdata = FormatSupport.parseString(attributes,
413: data);
414: contact.addString(Contact.PHOTO_URL, attr, sdata);
415: } else {
416: // ignore; value type not recognized
417: }
418: break;
419: }
420: case Contact.PUBLIC_KEY: {
421: String encoding = FormatSupport.getEncoding(attributes);
422: if (encoding.equals(FormatSupport.PLAIN_TEXT)) {
423: String sdata = FormatSupport.parseString(attributes,
424: data);
425: contact.addString(Contact.PUBLIC_KEY_STRING, attr,
426: sdata);
427: } else {
428: byte[] bdata = FormatSupport.parseBinary(attributes,
429: data);
430: if (bdata.length != 0) {
431: contact.addBinary(Contact.PUBLIC_KEY, attr, bdata,
432: 0, bdata.length);
433: }
434: }
435: break;
436: }
437: case Contact.CLASS: {
438: int i = VCardSupport.getClassCode(data);
439: if (i != -1) {
440: contact.addInt(Contact.CLASS, Contact.ATTR_NONE, i);
441: }
442: break;
443: }
444: default:
445: // System.err.println("No match for field prefix '"
446: // + prefix + "' (REMOVE THIS MESSAGE)");
447: }
448: }
449:
450: }
|