Home | History | Annotate | Download | only in imps
      1 /*
      2  * Copyright (C) 2007-2008 Esmertec AG.
      3  * Copyright (C) 2007-2008 The Android Open Source Project
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.im.imps;
     19 
     20 import java.io.IOException;
     21 import java.io.OutputStream;
     22 import java.io.OutputStreamWriter;
     23 import java.io.Writer;
     24 import java.util.ArrayList;
     25 import java.util.HashMap;
     26 import java.util.regex.Matcher;
     27 import java.util.regex.Pattern;
     28 
     29 import com.android.im.imps.ImpsConstants.ImpsVersion;
     30 
     31 public class PtsPrimitiveSerializer implements PrimitiveSerializer {
     32 
     33     private final String mPreampleHead;
     34 
     35     // The ccc is the Transaction-ID in range 0-999 without preceding zero.
     36     private static final Pattern sTxIdPattern = Pattern.compile("(0|[1-9]\\d{0,2})");
     37 
     38     // If the value of the parameter contains spaces ( ), quotes ("),
     39     // commas (,),parentheses (()),equal (=) or ampersand (&) characters,
     40     // it SHALL be wrapped with quotes (").
     41     private static final Pattern sCharsToBeQuoted = Pattern.compile("[ \",\\(\\)=&]");
     42 
     43     public PtsPrimitiveSerializer(ImpsVersion impsVersion) throws SerializerException {
     44         if (impsVersion == ImpsVersion.IMPS_VERSION_11) {
     45             mPreampleHead = "WV11";
     46         }else if (impsVersion == ImpsVersion.IMPS_VERSION_12) {
     47             mPreampleHead = "WV12";
     48         } else if (impsVersion == ImpsVersion.IMPS_VERSION_13) {
     49             mPreampleHead = "WV13";
     50         } else {
     51             throw new SerializerException("Unsupported IMPS version");
     52         }
     53     }
     54 
     55     public void serialize(Primitive p, OutputStream out)
     56             throws IOException, SerializerException {
     57         String txId = p.getTransactionID();
     58         if (txId == null) {
     59             if (!ImpsTags.Polling_Request.equals(p.getType())) {
     60                 throw new SerializerException("null Transaction-ID for non polling request");
     61             }
     62             // FIXME: what should this be? Temporarily use 0
     63             txId = "0";
     64         } else {
     65             Matcher m = sTxIdPattern.matcher(txId);
     66             if (!m.matches()) {
     67                 throw new SerializerException(
     68                         "Transaction-ID must be in range 0-999 without preceding zero");
     69             }
     70         }
     71 
     72         // TODO: use buffered writer?
     73         Writer writer = new OutputStreamWriter(out, "UTF-8");
     74         writer.write(mPreampleHead);
     75 
     76         String code = PtsCodes.getTxCode(p.getType());
     77         if (code == null) {
     78             throw new SerializerException("Unsupported transaction type "
     79                     + p.getType());
     80         }
     81         writer.write(code);
     82         writer.write(txId);
     83 
     84         if (p.getSessionId() != null) {
     85             writer.write(" SI=");
     86             writer.write(p.getSessionId());
     87         }
     88 
     89         PrimitiveElement content = p.getContentElement();
     90         if (content != null && content.getChildCount() > 0) {
     91             ArrayList<PrimitiveElement> infoElems = content.getChildren();
     92             ArrayList<String> users = new ArrayList<String>();
     93             ArrayList<String> lists = new ArrayList<String>();
     94 
     95             int len = infoElems.size();
     96             for (int i = 0; i < len; i++) {
     97                 PrimitiveElement elem = infoElems.get(i);
     98                 String elemName = elem.getTagName();
     99 
    100                 // workaround for multiple elements
    101                 if (ImpsTags.User.equals(elemName)) {
    102                     users.add(elem.getChildContents(ImpsTags.UserID));
    103                     continue;
    104                 } else if (ImpsTags.UserID.equals(elemName)) {
    105                     users.add(elem.getContents());
    106                     continue;
    107                 } else if (ImpsTags.ContactList.equals(elemName)) {
    108                     lists.add(elem.getContents());
    109                     continue;
    110                 }
    111 
    112                 String elemCode = PtsCodes.getElementCode(elemName, p.getType());
    113                 if (elemCode == null) {
    114                     throw new SerializerException("Don't know how to encode element "
    115                             + elemName);
    116                 }
    117                 writer.write(' ');
    118                 writer.write(elemCode);
    119                 // so far all top level information elements have values.
    120                 writer.write('=');
    121 
    122                 String value;
    123                 ElemValueEncoder encoder = ElemValueEncoder.getEncoder(elemName);
    124                 if (encoder == null) {
    125                     // default simple value
    126                     value = escapeValueString(elem.getContents());
    127                 } else {
    128                     value = encoder.encodeValue(p, elem);
    129                 }
    130                 if (value == null) {
    131                     throw new SerializerException("Empty value for element "
    132                             + elemName);
    133                 }
    134                 writer.write(value);
    135             }
    136 
    137             writeMultiValue(writer, PtsCodes.getElementCode(ImpsTags.UserID, p.getType()), users);
    138             writeMultiValue(writer, PtsCodes.getElementCode(ImpsTags.ContactList, p.getType()), lists);
    139         }
    140         writer.close();
    141     }
    142 
    143     private void writeMultiValue(Writer writer, String code, ArrayList<String> values)
    144             throws IOException {
    145         if (values.size() == 0) {
    146             return;
    147         }
    148 
    149         writer.write(' ');
    150         writer.write(code);
    151         writer.write('=');
    152         if (values.size() == 1) {
    153             writer.write(escapeValueString(values.get(0)));
    154         } else {
    155             writer.write('(');
    156             int valueCount = values.size();
    157             for (int i = 0; i < valueCount; i++) {
    158                 if (i > 0) {
    159                     writer.write(',');
    160                 }
    161                 writer.write(escapeValueString(values.get(i)));
    162             }
    163             writer.write(')');
    164         }
    165     }
    166 
    167     static String escapeValueString(String contents) {
    168         Matcher m = sCharsToBeQuoted.matcher(contents);
    169         if (m.find()) {
    170             if (contents.indexOf('"') != -1) {
    171                 contents = contents.replace("\"", "\"\"");
    172             }
    173             return "\"" + contents + "\"";
    174         }
    175         return contents;
    176     }
    177 
    178     static void appendPairValue(StringBuilder buf, String first, String second) {
    179         buf.append('(');
    180         if (first != null) {
    181             buf.append(first);
    182         }
    183         buf.append(',');
    184         buf.append(second);
    185         buf.append(')');
    186     }
    187 
    188     /**
    189      * Appends a name and value pair like "(<name>,<value>)".
    190      */
    191     static boolean appendNameAndValue(StringBuilder buf, String name, String value,
    192             HashMap<String, String> nameCodes, HashMap<String, String> valueCodes,
    193             boolean ignoreUnsupportedValue) {
    194         String nameCode = nameCodes.get(name);
    195         if (nameCode == null) {
    196             ImpsLog.log("PTS: Ignoring value " + name);
    197             return false;
    198         }
    199         String valueCode = null;
    200         if (valueCodes != null) {
    201             valueCode = valueCodes.get(value);
    202         }
    203         if (valueCode != null) {
    204             value = valueCode;
    205         } else {
    206             if (ignoreUnsupportedValue) {
    207                 return false;
    208             }
    209 
    210             value = escapeValueString(value);
    211         }
    212         appendPairValue(buf, nameCode, value);
    213 
    214         return true;
    215     }
    216 
    217     static abstract class ElemValueEncoder {
    218         public abstract String encodeValue(Primitive p, PrimitiveElement elem)
    219                 throws SerializerException;
    220 
    221         public static ElemValueEncoder getEncoder(String elemName) {
    222             return sEncoders.get(elemName);
    223         }
    224 
    225         private static HashMap<String, ElemValueEncoder> sEncoders;
    226         static {
    227             sEncoders = new HashMap<String, ElemValueEncoder>();
    228 
    229             sEncoders.put(ImpsTags.ClientID, new ClientIdEncoder());
    230             sEncoders.put(ImpsTags.CapabilityList, new CapabilityEncoder());
    231             sEncoders.put(ImpsTags.Functions, new ServiceTreeEncoder());
    232             sEncoders.put(ImpsTags.Result, new ResultEncoder());
    233             sEncoders.put(ImpsTags.ContactListProperties, new ProperitiesEncoder(
    234                     PtsCodes.sContactListPropsToCode));
    235             sEncoders.put(ImpsTags.PresenceSubList, new PresenceSubListEncoder());
    236 
    237             ElemValueEncoder nickListEncoder = new NickListEncoder();
    238             sEncoders.put(ImpsTags.NickList, nickListEncoder);
    239             sEncoders.put(ImpsTags.AddNickList, nickListEncoder);
    240             sEncoders.put(ImpsTags.RemoveNickList, nickListEncoder);
    241         }
    242     }
    243 
    244     static class PresenceSubListEncoder extends ElemValueEncoder {
    245         private boolean mEncodePresenceValue;
    246         @Override
    247         public String encodeValue(Primitive p, PrimitiveElement elem)
    248                 throws SerializerException {
    249             if (elem.getChildCount() == 0) {
    250                 throw new SerializerException("No presence in the PresenceSubList");
    251             }
    252 
    253             StringBuilder buf = new StringBuilder();
    254             mEncodePresenceValue = ImpsTags.UpdatePresence_Request.equals(p.getType());
    255 
    256             ArrayList<PrimitiveElement> presences = elem.getChildren();
    257             int presenceCount = presences.size();
    258             if (presenceCount == 1) {
    259                 if (mEncodePresenceValue) {
    260                     // Append an extra pair of braces according to the Spec
    261                     buf.append('(');
    262                     encodePresence(buf, presences.get(0));
    263                     buf.append(')');
    264                 } else {
    265                     encodePresence(buf, presences.get(0));
    266                 }
    267             } else {
    268                 buf.append('(');
    269                 for (int i = 0; i < presenceCount; i++) {
    270                     if (i > 0) {
    271                         buf.append(',');
    272                     }
    273                     encodePresence(buf, presences.get(i));
    274                 }
    275                 buf.append(')');
    276             }
    277 
    278             return buf.toString();
    279         }
    280 
    281         private void encodePresence(StringBuilder buf, PrimitiveElement p)
    282                 throws SerializerException {
    283             boolean hasQualifier = p.getChild(ImpsTags.Qualifier) != null;
    284             String presenceName = p.getTagName();
    285             String presenceNameCode = getPresenceCode(presenceName);
    286 
    287             if (!mEncodePresenceValue) {
    288                 encodeNoValuePresence(buf, p);
    289             } else {
    290                 buf.append('(');
    291                 buf.append(presenceNameCode);
    292                 buf.append(',');
    293                 if (hasQualifier) {
    294                     buf.append(p.getChildContents(ImpsTags.Qualifier));
    295                     buf.append(',');
    296                 }
    297                 // All the presences with value have this kind of structure:
    298                 // <name, qualifier, value>
    299                 // And for the values, there are three different hierarchies:
    300                 // 1. Simply use PresenceValue to indicate the value, most of the
    301                 //    presences has adapted this way. -> SingleValue
    302                 // 2. Use special tags for multiple values of this presence, eg. ClientInfo
    303                 //    has adapted this way. -> MultiValue
    304                 // 3. Has one or more children for the presence, and each child have
    305                 //    multiple values. eg. CommCap has adapted this way. -> ExtMultiValue
    306                 if (isMultiValuePresence(presenceName)) {
    307                     // condition 2: multiple value
    308                     int emptyValueSize = hasQualifier ? 1 : 0;
    309 
    310                     ArrayList<PrimitiveElement> children = p.getChildren();
    311                     if (children.size() > emptyValueSize) {
    312                         buf.append('(');
    313                         int childCount = children.size();
    314                         int j = 0;  // used for first value check
    315                         for (int i = 0; i < childCount; i++, j++) {
    316                             PrimitiveElement value = children.get(i);
    317                             if (ImpsTags.Qualifier.equals(value.getTagName())) {
    318                                 j--;
    319                                 continue;
    320                             }
    321 
    322                             if (j > 0) {
    323                                 buf.append(',');
    324                             }
    325                             buf.append('(');
    326                             buf.append(getPresenceCode(value.getTagName()));
    327                             buf.append(',');
    328                             buf.append(PtsCodes.getPAValueCode(value.getContents()));
    329                             buf.append(')');
    330                         }
    331                         buf.append(')');
    332                     }
    333                 } else if (isExtMultiValuePresence(presenceName)) {
    334                     // condition 3: extended multiple value
    335                     // TODO: Implementation
    336                 } else {
    337                     // Condition 1: single value
    338                     if (p.getChild(ImpsTags.PresenceValue) == null) {
    339                         throw new SerializerException("Can't find presence value for " + presenceName);
    340                     }
    341                     buf.append(PtsCodes.getPAValueCode(p.getChildContents(ImpsTags.PresenceValue)));
    342                 }
    343                 buf.append(')');
    344             }
    345         }
    346 
    347         private void encodeNoValuePresence(StringBuilder buf, PrimitiveElement p)
    348                 throws SerializerException {
    349             if (p.getChildCount() == 0) {
    350                 buf.append(getPresenceCode(p.getTagName()));
    351             } else {
    352                 ArrayList<PrimitiveElement> children = p.getChildren();
    353                 int childCount = children.size();
    354                 buf.append('(');
    355                 buf.append(getPresenceCode(p.getTagName()));
    356                 buf.append(",(");
    357                 for (int i = 0; i < childCount; i++) {
    358                     if (i > 0) {
    359                         buf.append(',');
    360                     }
    361 
    362                     encodeNoValuePresence(buf, children.get(i));
    363                 }
    364                 buf.append("))");
    365             }
    366         }
    367 
    368         private String getPresenceCode(String tagname) throws SerializerException {
    369             String code = PtsCodes.getPresenceAttributeCode(tagname);
    370             if (code == null) {
    371                 throw new SerializerException("Unsupport presence attribute: " + tagname);
    372             }
    373 
    374             return code;
    375         }
    376 
    377         private boolean isMultiValuePresence(String presenceName) {
    378             if (ImpsTags.ClientInfo.equals(presenceName)) {
    379                 return true;
    380             }
    381 
    382             // TODO: Add more supported extended multiple presence here
    383             return false;
    384         }
    385 
    386         private boolean isExtMultiValuePresence(String presenceName) {
    387             // TODO: Add supported extended multiple presence here
    388             return false;
    389         }
    390     }
    391 
    392     static class ClientIdEncoder extends ElemValueEncoder {
    393         @Override
    394         public String encodeValue(Primitive p, PrimitiveElement elem)
    395                 throws SerializerException {
    396             String value = elem.getChildContents(ImpsTags.URL);
    397             if (value == null) {
    398                 value = elem.getChildContents(ImpsTags.MSISDN);
    399             }
    400 
    401             return escapeValueString(value);
    402         }
    403     }
    404 
    405     static class CapabilityEncoder extends ElemValueEncoder {
    406         @Override
    407         public String encodeValue(Primitive p, PrimitiveElement elem)
    408                 throws SerializerException {
    409             ArrayList<PrimitiveElement> caps = elem.getChildren();
    410             int i, len;
    411             StringBuilder result = new StringBuilder();
    412             result.append('(');
    413             for (i = 0, len = caps.size(); i < len; i++) {
    414                 PrimitiveElement capElem = caps.get(i);
    415                 String capName = capElem.getTagName();
    416                 String capValue = capElem.getContents();
    417 
    418                 if (i > 0) {
    419                     result.append(',');
    420                 }
    421                 if (!appendNameAndValue(result, capName, capValue,
    422                         PtsCodes.sCapElementToCode, PtsCodes.sCapValueToCode,
    423                         ImpsTags.SupportedCIRMethod.equals(capName))) {
    424                     result.deleteCharAt(result.length() - 1);
    425                 }
    426             }
    427             result.append(')');
    428             return result.toString();
    429         }
    430     }
    431 
    432     static class ServiceTreeEncoder extends ElemValueEncoder {
    433         @Override
    434         public String encodeValue(Primitive p, PrimitiveElement elem)
    435                 throws SerializerException {
    436             StringBuilder buf = new StringBuilder();
    437             buf.append('(');
    438             appendFeature(buf, elem.getFirstChild());
    439             buf.append(')');
    440             return buf.toString();
    441         }
    442 
    443         private void appendFeature(StringBuilder buf, PrimitiveElement elem)
    444                 throws SerializerException {
    445             int childCount = elem.getChildCount();
    446             if (childCount > 0) {
    447                 ArrayList<PrimitiveElement> children = elem.getChildren();
    448                 for (int i = 0; i < childCount; i++) {
    449                     appendFeature(buf, children.get(i));
    450                 }
    451             } else {
    452                 String code = PtsCodes.getServiceTreeCode(elem.getTagName());
    453                 if (code == null) {
    454                     throw new SerializerException("Invalid service tree tag:"
    455                             + elem.getTagName());
    456                 }
    457                 if (buf.length() > 1) {
    458                     buf.append(',');
    459                 }
    460                 buf.append(code);
    461             }
    462         }
    463     }
    464 
    465     static class ResultEncoder extends ElemValueEncoder {
    466         @Override
    467         public String encodeValue(Primitive p, PrimitiveElement elem)
    468                 throws SerializerException {
    469             String code = elem.getChildContents(ImpsTags.Code);
    470             String desc = elem.getChildContents(ImpsTags.Description);
    471             // Client never sends partial success result, the DetailedResult is
    472             // ignored.
    473             if (desc == null) {
    474                 return code;
    475             } else {
    476                 StringBuilder res = new StringBuilder();
    477                 appendPairValue(res, code, escapeValueString(desc));
    478                 return res.toString();
    479             }
    480         }
    481     }
    482 
    483     static class NickListEncoder extends ElemValueEncoder {
    484         @Override
    485         public String encodeValue(Primitive p, PrimitiveElement elem)
    486                 throws SerializerException {
    487             StringBuilder buf = new StringBuilder();
    488             ArrayList<PrimitiveElement> children = elem.getChildren();
    489             int count = children.size();
    490             buf.append('(');
    491             for (int i = 0; i < count; i++) {
    492                 PrimitiveElement child = children.get(i);
    493                 String tagName = child.getTagName();
    494                 String nickName = null;
    495                 String userId = null;
    496                 if (tagName.equals(ImpsTags.NickName)) {
    497                     nickName = child.getChildContents(ImpsTags.Name);
    498                     userId = child.getChildContents(ImpsTags.UserID);
    499                 } else if (tagName.equals(ImpsTags.UserID)) {
    500                     userId = child.getContents();
    501                 }
    502                 if (i > 0) {
    503                     buf.append(',');
    504                 }
    505                 if (nickName != null) {
    506                     nickName = escapeValueString(nickName);
    507                 }
    508                 appendPairValue(buf, nickName, escapeValueString(userId));
    509             }
    510             buf.append(')');
    511             return buf.toString();
    512         }
    513     }
    514 
    515     static class ProperitiesEncoder extends ElemValueEncoder {
    516         private HashMap<String, String> mPropNameCodes;
    517 
    518         public ProperitiesEncoder(HashMap<String, String> propNameCodes) {
    519             mPropNameCodes = propNameCodes;
    520         }
    521 
    522         @Override
    523         public String encodeValue(Primitive p, PrimitiveElement elem)
    524                 throws SerializerException {
    525             ArrayList<PrimitiveElement> props = elem.getChildren();
    526             StringBuilder result = new StringBuilder();
    527             result.append('(');
    528             int count = props.size();
    529             for (int i = 0; i < count; i++) {
    530                 PrimitiveElement property = props.get(i);
    531                 String name;
    532                 String value;
    533                 if (property.getTagName().equals(ImpsTags.Property)) {
    534                     name = property.getChildContents(ImpsTags.Name);
    535                     value = property.getChildContents(ImpsTags.Value);
    536                 } else {
    537                     name = property.getTagName();
    538                     value = property.getContents();
    539                 }
    540                 if (i > 0) {
    541                     result.append(',');
    542                 }
    543                 appendNameAndValue(result, name, value, mPropNameCodes, null, false);
    544             }
    545             result.append(')');
    546             return result.toString();
    547         }
    548     }
    549 }
    550