/* * Funambol is a mobile platform developed by Funambol, Inc. * Copyright (C) 2003 - 2007 Funambol, Inc. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation with the addition of the following permission * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * 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 for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program; if not, see http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. * * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License * version 3, these Appropriate Legal Notices must retain the display of the * "Powered by Funambol" logo. If the display of the logo is not reasonably * feasible for technical reasons, the Appropriate Legal Notices must display * the words "Powered by Funambol". */ package com.funambol.common.pim.model.converter; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.Vector; import java.util.Iterator; import java.util.TimeZone; import com.funambol.util.QuotedPrintable; import com.funambol.common.pim.model.common.*; import com.funambol.common.pim.model.utility.TimeUtils; /** * Represent a converter base class. Provides some common methods. */ public abstract class BaseConverter { // --------------------------------------------------------------- Constants public final static String ENCODING_QP = "QUOTED-PRINTABLE"; public final static String CHARSET_UTF8 = "UTF-8" ; public final static String CHARSET_UTF7 = "UTF-7" ; public final static String ENCODING_B64 = "BASE64" ; public static final String PLAIN_CHARSET = "plain" ; // -------------------------------------------------------------- Properties protected TimeZone timezone = null ; protected String charset = null ; protected boolean forceClientLocalTime = false; /** Specifies the list of supported fields */ protected Vector<String> supportedFields = null; // ------------------------------------------------------------- Constructor /** * This constructor is deprecated because to handle the date is need to know * timezone but also if the dates must be converted in local time. * * @param timezone the timezone to use in the conversion * @param charset the charset to use in the conversion * @deprecated */ @Deprecated public BaseConverter(TimeZone timezone, String charset) { this.timezone = timezone; this.charset = charset; } /** * * @param timezone the timezone to use in the conversion * @param charset the charset to use in the conversion * @param forceDeviceLocalTime true if the date must be converted in the * device's local time, false otherwise. */ public BaseConverter(TimeZone timezone, String charset, boolean forceDeviceLocalTime) { this.timezone = timezone; this.charset = charset; this.forceClientLocalTime = forceDeviceLocalTime; } // ---------------------------------------------------------- Public Methods /** * Converts the given sDate in UTC * using the given timezone * @param sDate String * @param timezone TimeZone * @return String * @throws ConverterException */ public static String handleConversionToUTCDate(String sDate , TimeZone timezone) throws ConverterException { try { sDate = TimeUtils.convertLocalDateToUTC(sDate, timezone); } catch (Exception ex) { throw new ConverterException("Error converting date " + sDate + " into UTC format."); } return sDate; } /** * Converts the given sDate it in local time * using the given timezone * @param sDate String * @param timezone TimeZone * @return String * @throws ConverterException */ public static String handleConversionToLocalDate(String sDate , TimeZone timezone) throws ConverterException { if (timezone == null) { return sDate; } try { sDate = TimeUtils.convertUTCDateToLocal(sDate, timezone); } catch (Exception ex) { throw new ConverterException("Error converting date " + sDate + " into local timezone format."); } return sDate; } /** * Converts the given sDate it in the local time of another time zone. * * @param sDate String * @param timezoneIn TimeZone * @param timezoneOut TimeZone * @return String * @throws ConverterException */ public static String handleConversionAcrossTimeZones(String sDate , TimeZone timezoneIn , TimeZone timezoneOut) throws ConverterException { try { sDate = TimeUtils.convertDateFromTo(sDate, TimeUtils.PATTERN_UTC_WOZ, timezoneIn, timezoneOut); } catch (Exception ex) { throw new ConverterException("Error converting date " + sDate + " across time zones."); } return sDate; } /** * Converts the given sDate in all-day format * using the given timezone * @param sDate as a String, in local time format * @return String * @throws ConverterException */ public static String handleConversionToAllDayDate(String sDate) throws ConverterException { try { sDate = TimeUtils.convertDateFromTo(sDate, TimeUtils.PATTERN_YYYY_MM_DD); } catch (Exception ex) { throw new ConverterException("Error converting date " + sDate + " into all-day format."); } return sDate; } /** * Converts the given sDate in all-day format * <br>In the conversion the following rules are applied: * <ul> * <li>if the given timezoneIn is not null, it is applied on the stringDate * conversion * </li> * <li>if the given timezoneOut is not null, it is applied on the output date * </li> * </ul> * @param sDate the date to convert * @param timezoneIn the timezone to apply to the given date * @param timezoneOut the timezone to apply on the output date * @return String the date into proper format * @throws ConverterException if an error occurs */ public static String handleConversionToAllDayDate(String sDate, TimeZone timezoneIn, TimeZone timezoneOut) throws ConverterException { try { sDate = TimeUtils.convertDateFromTo(sDate, TimeUtils.PATTERN_YYYY_MM_DD, timezoneIn, timezoneOut); } catch (Exception ex) { throw new ConverterException("Error converting date " + sDate + " into all-day format."); } return sDate; } /** * Replace values with proper encoding * * @param value the value to apply the replacement * @param encoding the encoding to use (default is QUOTED-PRINTABLE) * @param charset the charset to use (default is UTF-8) * @return String the value replaced */ public String encode(String value, String encoding, String charset) throws ConverterException { try { if (value == null) { return value; } // // If input charset is null then set it with default value as UTF-8 // if (charset == null) { charset = CHARSET_UTF8; } if (ENCODING_B64.equals(encoding)) { // // truncate the b64 encoded text into lines of no more that 76 chars // StringBuffer sb = new StringBuffer(); sb.append("\r\n "); while(value.length() > 75) { sb.append(value.substring(0,75)); sb.append("\r\n"); sb.append(" "); value = value.substring(75); } sb.append(value); sb.append("\r\n"); value = sb.toString(); } else if (ENCODING_QP.equals(encoding)) { byte b[] = value.getBytes(charset); b = QuotedPrintable.encode(b); value = new String(b, charset); } } catch(UnsupportedEncodingException e) { String msg = "The Character Encoding (" + charset + ") is not supported"; throw new ConverterException(msg); } return value; } /** * This method compose the single component of vCard and in particular: * 1) encode value with the proper encoding * 2) handle the params * 3) create a representation of the specificated vCard field * * @param propertyValue the value of vCard field * @param properties a vector of params * @param field the filed name * * @return String the representation of the specificated vCard field * @throws com.funambol.common.pim.converter.ConverterException if an error occurs */ public StringBuffer composeVCardComponent(String propertyValue, ArrayList properties, String field ) throws ConverterException { return composeVCardComponent(propertyValue, properties, field, false); } /** * This method compose the single component of vCard and in particular: * 1) encode value with the proper encoding * 2) handle the params * 3) create a representation of the specificated vCard field * * @param propertyValue the value of vCard field * @param properties a vector of params * @param field the filed name * * @param excludeCharset must the charset be not set ? * @return String the representation of the given vCard field * @throws com.funambol.common.pim.converter.ConverterException if an error occurs */ public StringBuffer composeVCardComponent(String propertyValue, ArrayList properties, String field , boolean excludeCharset) throws ConverterException { StringBuffer result = new StringBuffer(120); try { String group = getGrouping(properties); if (group != null && !group.equals("")) { result.append(group).append("."); } result.append(field); String encodingParam = getEncoding(properties); if (encodingParam == null) { encodingParam = ENCODING_QP; } String charsetParam = getCharset(properties); if (charsetParam == null) { if (charset == null) { charsetParam = CHARSET_UTF8; } else { charsetParam = charset; } } if (propertyValue == null) { propertyValue = ""; } else { if (!PLAIN_CHARSET.equalsIgnoreCase(charsetParam)) { // // We encode the value only if the charset isn't PLAIN_CHARSET // // At this level we have always an ENCODING (at least QP) // propertyValue = encode(propertyValue, encodingParam, charsetParam); // // We set the ENCODING and the CHARSET only if: // 1. we are using the QP and the result doesn't contain any '=' // (the value doesn't contain chars to encode) // or // 2. we have a different encoding from QP // (in this way we preserve the original property encoding) // if (ENCODING_QP.equalsIgnoreCase(encodingParam) && propertyValue.indexOf("=") != -1) { result.append(";ENCODING=").append(encodingParam); if (!excludeCharset) { result.append(";CHARSET=").append(charsetParam); } } else if (!ENCODING_QP.equalsIgnoreCase(encodingParam)) { result.append(";ENCODING=").append(encodingParam); if (!excludeCharset) { result.append(";CHARSET=").append(charsetParam); } } } } String languageParam = getLanguage(properties); if (languageParam != null) { result.append(";LANGUAGE=").append(languageParam); } String valueParam = getValue(properties); if (valueParam != null) { result.append(";VALUE=").append(valueParam); } String typeParam = getType(properties); if (typeParam != null) { result.append(";TYPE=").append(typeParam); } result.append(":").append(propertyValue).append("\r\n"); } catch(Exception e) { throw new ConverterException("Error composing VCard component "); } // Once formatted remove the property from the supported list if(supportedFields != null) { supportedFields.remove(field); } return result; } /** * This method compose the single component of iCalendar and in particular: * 1) encode value with the proper encoding * 2) handle the params * 3) create a representation of the specificated iCalendar field * * @param value the value of iCalendar field * @param properties a vector of params * @param field the field name * @return the representation of the specificated iCalendar field as a * StringBuffer object * * @deprecated Unused since version 6.5, replaced by methods in * {@link #com.funambol.common.pim.converter.CalendarContentConverter} */ public StringBuffer composeICalTextComponent(Property property, String field) throws ConverterException { StringBuffer result = new StringBuffer(240); // Estimate 240 is needed result.append(field); String propertyValue = escapeSeparator((String)property.getPropertyValue()); try { // // Encode value as QUOTED-PRINTABLE and set encodingParam at the // default value (at the moment we handle only ENCODING=QUOTED-PRINTABLE) // String encodingParam = ENCODING_QP; String charsetParam = property.getCharset(); if (charsetParam == null) { if (charset == null) { charsetParam = CHARSET_UTF8; } else { charsetParam = charset; } } if (propertyValue == null) { propertyValue = ""; } else { if (!PLAIN_CHARSET.equalsIgnoreCase(charsetParam)) { // // We encode the value only if the charset isn't PLAIN_CHARSET // propertyValue = encode(propertyValue, encodingParam, charsetParam); if (propertyValue.indexOf("=") != -1) { result.append(";ENCODING=").append(encodingParam); result.append(";CHARSET=").append(charsetParam); } } } String altrepParam = property.getAltrep(); if (altrepParam != null) { result.append(";ALTREP=").append(altrepParam); } String languageParam = property.getLanguage(); if (languageParam != null) { result.append(";LANGUAGE=").append(languageParam); } String cnParam = property.getCn(); if (cnParam != null) { result.append(";CN=").append(cnParam); } String cuttypeParam = property.getCutype(); if (cuttypeParam != null) { result.append(";CUTYPE=").append(cuttypeParam); } String delegatedFromParam = property.getDelegatedFrom(); if (delegatedFromParam != null) { result.append(";DELEGATED-FROM=").append(delegatedFromParam); } String delegatedToParam = property.getDelegatedTo(); if (delegatedToParam != null) { result.append(";DELEGATED-TO=").append(delegatedToParam); } String dirParam = property.getDir(); if (dirParam != null) { result.append(";DIR=").append(dirParam); } String memberParam = property.getMember(); if (memberParam != null) { result.append(";MEMBER=").append(memberParam); } String partstatParam = property.getPartstat(); if (partstatParam != null) { result.append(";PARTSTAT=").append(partstatParam); } String relatedParam = property.getRelated(); if (relatedParam != null) { result.append(";RELATED=").append(relatedParam); } String sentbyParam = property.getSentby(); if (sentbyParam != null) { result.append(";SENT-BY=\"").append(sentbyParam).append("\""); } String valueParam = property.getValue(); if (valueParam != null) { result.append(";VALUE=").append(valueParam); } result.append(getXParams(property)); result.append(":").append(propertyValue).append("\r\n"); } catch(Exception e) { throw new ConverterException("Error composing iCalendar component "); } return result; } /** * Compose the remaining fields from the supportedFields Vector. * @return */ protected StringBuffer composeRemainingFields() { StringBuffer result = new StringBuffer(); if(supportedFields != null && supportedFields.size() > 0) { for(int i=0; i<supportedFields.size(); i++) { String field = supportedFields.elementAt(i); String value = ""; if(field.equals("ORG")) { value = ";"; } else if(field.startsWith("ADR")) { value = ";;;;;;"; } result.append(field).append(":").append(value).append("\r\n"); } } return result; } /** * A SEMI-COLON in a property value MUST be escaped with a '\' character. * A BACKSLASH in a property value MUST be escaped with a '\' character. * * @param value the value in which replaced the ; and the \ */ public String escapeSeparator(String value) { String tmp = value.replaceAll("\\\\", "\\\\\\\\"); tmp = tmp.replaceAll(";", "\\\\;"); return tmp; } // --------------------------------------------------------- Private Methods private String getEncoding(ArrayList properties) { for (int i=0;i<properties.size();i++) { if (((Property)properties.get(i)).getEncoding()!=null) { return ((Property)properties.get(i)).getEncoding(); } } return null; } private String getCharset(ArrayList properties) { for (int i=0;i<properties.size();i++) { if (((Property)properties.get(i)).getCharset()!=null) { return ((Property)properties.get(i)).getCharset(); } } return null; } private String getGrouping(ArrayList properties) { for (int i=0;i<properties.size();i++) { if (((Property)properties.get(i)).getGroup()!=null) return ((Property)properties.get(i)).getGroup(); } return null; } private String getLanguage(ArrayList properties) { for (int i=0;i<properties.size();i++) { if (((Property)properties.get(i)).getLanguage()!=null) { return ((Property)properties.get(i)).getLanguage(); } } return null; } private String getValue(ArrayList properties) { for (int i=0;i<properties.size();i++) { if (((Property)properties.get(i)).getValue()!=null) return ((Property)properties.get(i)).getValue(); } return null; } private String getType(ArrayList properties) { for (int i=0;i<properties.size();i++) { if (((Property)properties.get(i)).getType()!=null) { return ((Property)properties.get(i)).getType(); } } return null; } private StringBuffer getXParams(Property property) { ArrayList properties = new ArrayList(); properties.add(property); return getXParams(properties); } private StringBuffer getXParams(ArrayList properties) { HashMap hm = null; Iterator it = null; Property xtagProp = null; String tag = null; String value = null; StringBuffer params = new StringBuffer(); for (int i=0;i<properties.size();i++) { xtagProp = (Property)properties.get(i); hm = xtagProp.getXParams(); it = hm.keySet().iterator(); while(it.hasNext()) { tag = (String)it.next(); value = (String)hm.get(tag); // // If tag is the same as value then this tag is handle as // param without value // if (tag.equals(value) || value == null) { params.append(";").append(tag); } else { params.append(";").append(tag).append("=").append(value); } } } return params; } }