2 * Copyright (C) 2007 Esmertec AG.
3 * Copyright (C) 2007 The Android Open Source Project
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 package com.android.im.imps;
20 import java.io.BufferedReader;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InputStreamReader;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.Map.Entry;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
30 import com.android.im.imps.Primitive.TransactionMode;
33 * PTS/SMS encoded IMPS messages parser. Only response transactions and
34 * server initiated requests are supported.
36 public class PtsPrimitiveParser implements PrimitiveParser {
38 // WVaaBBcccDD <parameters>
39 // aa - version number; 12 for 1.2, 13 for 1.3; "XX" for version discovery
40 // BB - message type, case insensitive
41 // ccc - transaction id in range 0-999 without preceding zero
42 // DD - multiple SMSes identifier
43 private static final Pattern sPreamplePattern =
44 Pattern.compile("\\AWV(\\d{2})(\\p{Alpha}{2})(\\d{1,3})(\\p{Alpha}{2})?(\\z| .*)");
46 private char mReadBuf[] = new char[256];
47 private StringBuilder mStringBuf = new StringBuilder();
50 private static int UNCERTAIN_GROUP_SIZE = -1;
52 public Primitive parse(InputStream in) throws ParserException, IOException {
53 // assuming PTS data is always short
54 BufferedReader reader = new BufferedReader(
55 new InputStreamReader(in, "UTF-8"), 128);
56 mStringBuf.setLength(0);
59 while ((len = reader.read(mReadBuf)) != -1) {
60 mStringBuf.append(mReadBuf, 0, len);
65 private Primitive parsePrim() throws ParserException
67 Matcher m = sPreamplePattern.matcher(mStringBuf);
69 throw new ParserException("Invalid PTS encoded message");
72 Primitive p = new Primitive();
74 // TODO: handle WV version in m.group(1)
76 String type = m.group(2).toUpperCase();
77 String transactionType = PtsCodes.getTransaction(type);
78 if (transactionType == null) {
79 throw new ParserException("Unrecognized transaction code " + type);
81 p.setContentElement(transactionType);
83 if (PtsCodes.isServerRequestCode(type)) {
84 p.setTransactionMode(TransactionMode.Request);
86 p.setTransactionMode(TransactionMode.Response);
89 p.setTransactionId(m.group(3));
92 if (mPos < mStringBuf.length()) {
95 HashMap<String, ParamValue> params = parseParams();
96 for (Entry<String, ParamValue> param : params.entrySet()) {
97 translateParam(p, param.getKey(), param.getValue());
103 private static HashMap<String, Integer> sInfoElemTypeMap;
104 private static final int ELEM_OTHER_SIMPLE = 0;
105 private static final int ELEM_SESSION_ID = 1;
106 private static final int ELEM_RESULT = 2;
107 private static final int ELEM_ALL_FUNCTIONS = 3;
108 private static final int ELEM_NOT_AVAIL_FUNCS = 4;
109 private static final int ELEM_CAPABILITY_LIST = 5;
110 private static final int ELEM_CONTACT_LIST = 6;
111 private static final int ELEM_DEFAULT_CONTACT_LIST = 7;
112 private static final int ELEM_USER_NICK_LIST = 8;
113 private static final int ELEM_CONTACT_LIST_PROPS = 9;
114 private static final int ELEM_PRESENCE = 10;
117 private static final int ELEM_RESULT_CLIST = 3;
118 private static final int ELEM_RESULT_DOMAIN = 4;
119 private static final int ELEM_RESULT_GROUP = 5;
120 private static final int ELEM_RESULT_MSGID = 6;
121 private static final int ELEM_RESULT_SCRNAME = 7;
122 private static final int ELEM_RESULT_USER = 8;
126 sInfoElemTypeMap = new HashMap<String, Integer>();
127 sInfoElemTypeMap.put(PtsCodes.SessionID, ELEM_SESSION_ID);
128 sInfoElemTypeMap.put(PtsCodes.Status, ELEM_RESULT);
129 sInfoElemTypeMap.put(PtsCodes.NotAvailableFunctions, ELEM_NOT_AVAIL_FUNCS);
130 sInfoElemTypeMap.put(PtsCodes.AllFunctions, ELEM_ALL_FUNCTIONS);
131 sInfoElemTypeMap.put(PtsCodes.AgreedCapabilityList, ELEM_CAPABILITY_LIST);
132 sInfoElemTypeMap.put(PtsCodes.ContactList, ELEM_CONTACT_LIST);
133 sInfoElemTypeMap.put(PtsCodes.DefaultContactList, ELEM_DEFAULT_CONTACT_LIST);
134 sInfoElemTypeMap.put(PtsCodes.UserNickList, ELEM_USER_NICK_LIST);
135 sInfoElemTypeMap.put(PtsCodes.ContactListProps, ELEM_CONTACT_LIST_PROPS);
136 sInfoElemTypeMap.put(PtsCodes.Presence, ELEM_PRESENCE);
139 private static void translateParam(Primitive p, String elemCode,
140 ParamValue elemValue) throws ParserException {
142 elemCode = elemCode.toUpperCase();
144 // FIXME: Should be refactored when we had concrete situation of the null value case
145 if (elemValue == null) {
146 throw new ParserException("Parameter " + elemCode + " must have value.");
149 if (sInfoElemTypeMap.containsKey(elemCode)) {
150 type = sInfoElemTypeMap.get(elemCode);
152 if (type == ELEM_RESULT_CLIST && p.getType().equals(ImpsTags.Login_Response)) {
153 // Fix up DigestSchema which shares a same code with
154 // ContactListID. It appears only in Login_Response.
155 type = ELEM_OTHER_SIMPLE;
159 type = ELEM_OTHER_SIMPLE;
163 case ELEM_SESSION_ID:
164 if (elemValue.mStrValue == null) {
165 throw new ParserException("Element SessionID must have string value!");
168 if (p.getType().equals(ImpsTags.Login_Response)) {
169 p.addElement(ImpsTags.SessionID, elemValue.mStrValue);
171 p.setSession(elemValue.mStrValue);
177 // ST=(<StatusCode>,<Description>)
178 PrimitiveElement result = p.addElement(ImpsTags.Result);
180 if (elemValue.mStrValue != null) {
181 result.addChild(ImpsTags.Code, elemValue.mStrValue);
183 checkGroupValue(elemValue.mValueGroup, 2);
185 result.addChild(ImpsTags.Code, elemValue.mValueGroup.get(0).mStrValue);
186 result.addChild(ImpsTags.Description, elemValue.mValueGroup.get(1).mStrValue);
190 case ELEM_ALL_FUNCTIONS:
191 case ELEM_NOT_AVAIL_FUNCS:
192 p.addElement(translateServiceTree(elemCode, elemValue));
195 case ELEM_CAPABILITY_LIST:
196 p.addElement(translateCapabilityList(elemValue));
199 case ELEM_CONTACT_LIST:
200 if (elemValue.mStrValue != null) {
201 p.addElement(ImpsTags.ContactList, elemValue.mStrValue);
203 checkGroupValue(elemValue.mValueGroup, UNCERTAIN_GROUP_SIZE);
204 for (ParamValue value : elemValue.mValueGroup) {
205 p.addElement(ImpsTags.ContactList, value.mStrValue);
210 case ELEM_DEFAULT_CONTACT_LIST:
211 if (elemValue.mStrValue == null) {
212 throw new ParserException("Deafult Contact List must have string value!");
215 p.addElement(ImpsTags.DefaultContactList, elemValue.mStrValue);
218 case ELEM_USER_NICK_LIST:
220 checkGroupValue(elemValue.mValueGroup, UNCERTAIN_GROUP_SIZE);
222 PrimitiveElement nicklistElem = p.addElement(ImpsTags.NickList);
224 int groupSize = elemValue.mValueGroup.size();
225 for (int i = 0; i < groupSize; i++) {
226 ArrayList<ParamValue> valueGroup = elemValue.mValueGroup.get(i).mValueGroup;
227 checkGroupValue(valueGroup, 2);
229 String nickname = valueGroup.get(0).mStrValue;
230 String address = valueGroup.get(1).mStrValue;
231 if (nickname == null || address == null) {
232 throw new ParserException("Null value found for NickName: " + nickname
236 PrimitiveElement nicknameElem = nicklistElem.addChild(ImpsTags.NickName);
237 nicknameElem.addChild(ImpsTags.Name, "".equals(nickname) ? null : nickname);
238 nicknameElem.addChild(ImpsTags.UserID, address);
243 case ELEM_CONTACT_LIST_PROPS:
245 checkGroupValue(elemValue.mValueGroup, UNCERTAIN_GROUP_SIZE);
247 PrimitiveElement propertiesElem = p.addElement(ImpsTags.ContactListProperties);
249 int groupSize = elemValue.mValueGroup.size();
250 for (int i = 0; i < groupSize; i++) {
251 ArrayList<ParamValue> valueGroup = elemValue.mValueGroup.get(i).mValueGroup;
252 checkGroupValue(valueGroup, 2);
254 String name = valueGroup.get(0).mStrValue;
255 String value = valueGroup.get(1).mStrValue;
256 if (name == null || value == null) {
257 throw new ParserException("Null value found for property: " + name + "-" + value);
260 if (PtsCodes.DisplayName.equals(name)) {
261 name = ImpsConstants.DisplayName;
262 } else if (PtsCodes.Default.equals(name)) {
263 name = ImpsConstants.Default;
265 throw new ParserException("Unrecognized property " + name);
268 PrimitiveElement propertyElem = propertiesElem.addChild(ImpsTags.Property);
269 propertyElem.addChild(ImpsTags.Name, name);
270 propertyElem.addChild(ImpsTags.Value, value);
276 //PR=(<UserID>[,<PresenceSubList>])
277 //PR=((<UserID>[,<PresenceSubList>]),(<UserID>[,<PresenceSubList>]))
278 checkGroupValue(elemValue.mValueGroup, UNCERTAIN_GROUP_SIZE);
280 if (elemValue.mValueGroup.size() == 1) {
282 ParamValue value = elemValue.mValueGroup.get(0);
283 if (value.mStrValue != null) {
284 p.addElement(ImpsTags.Presence).addChild(ImpsTags.UserID, value.mStrValue);
286 // workaround for OZ server
287 p.addElement(translatePresence(value.mValueGroup));
291 if (elemValue.mValueGroup.get(0).mStrValue == null) {
292 // PR=((<UserID>[,<PresenceSubList>]),(<UserID>[,<PresenceSubList>]))
293 int groupSize = elemValue.mValueGroup.size();
294 for (int i = 0; i < groupSize; i++) {
295 ParamValue value = elemValue.mValueGroup.get(i);
296 if (value.mStrValue != null) {
297 p.addElement(ImpsTags.Presence).addChild(ImpsTags.UserID, value.mStrValue);
299 p.addElement(translatePresence(value.mValueGroup));
303 // PR=(<UserID>,<PresenceSubList>)
304 p.addElement(translatePresence(elemValue.mValueGroup));
309 case ELEM_OTHER_SIMPLE:
310 p.addElement(translateSimpleElem(elemCode, elemValue));
314 throw new ParserException("Unsupported element " + elemValue);
318 private static PrimitiveElement translatePresence(ArrayList<ParamValue> valueGroup)
319 throws ParserException {
320 checkGroupValue(valueGroup, UNCERTAIN_GROUP_SIZE);
322 PrimitiveElement presence = new PrimitiveElement(ImpsTags.Presence);
323 if (valueGroup.get(0).mStrValue == null) {
324 throw new ParserException("UserID must have string value!");
326 presence.addChild(ImpsTags.UserID, valueGroup.get(0).mStrValue);
328 if (valueGroup.size() > 1) {
329 // has presence sub list
330 presence.addChild(translatePresenceSubList(valueGroup.get(1)));
336 private static PrimitiveElement translatePresenceSubList(ParamValue value)
337 throws ParserException {
338 checkGroupValue(value.mValueGroup, UNCERTAIN_GROUP_SIZE);
340 PrimitiveElement presenceSubList = new PrimitiveElement(ImpsTags.PresenceSubList);
342 int groupSize = value.mValueGroup.size();
343 for (int i = 0; i < groupSize; i++) {
344 ParamValue v = value.mValueGroup.get(i);
345 if (v.mStrValue != null) {
346 throw new ParserException("Unexpected string value for presence attribute");
349 presenceSubList.addChild(translatePresenceAttribute(v.mValueGroup));
352 return presenceSubList;
355 // <attribute>[,<qualifier>][,<value>]
356 // <attribute>[,<qualifier>,<sub-attribute>]
357 private static PrimitiveElement translatePresenceAttribute(
358 ArrayList<ParamValue> valueGroup) throws ParserException {
359 String type = valueGroup.get(0).mStrValue;
364 String tag = PtsCodes.getPresenceAttributeElement(type);
369 PrimitiveElement paElem = new PrimitiveElement(tag);
370 if (valueGroup.size() == 2) {
372 translateAttributeValue(paElem, valueGroup.get(1), false);
373 }else if (valueGroup.size() == 3) {
374 // has qualifier, and it should has no group value
375 ParamValue qualifierValue = valueGroup.get(1);
376 if (qualifierValue.mStrValue == null) {
377 throw new ParserException("Qualifier value can't be group value!");
380 if (!"".equals(qualifierValue.mStrValue)) {
381 paElem.addChild(ImpsTags.Qualifier, qualifierValue.mStrValue);
384 translateAttributeValue(paElem, valueGroup.get(2), true);
392 private static void translateAttributeValue(PrimitiveElement paElem,
393 ParamValue v, boolean hasQualifier) throws ParserException {
394 if (v.mStrValue == null) {
395 // sub-attribute as value
396 checkGroupValue(v.mValueGroup, UNCERTAIN_GROUP_SIZE);
397 if (v.mValueGroup.get(0).mStrValue != null) {
398 paElem.addChild(translatePresenceAttribute(v.mValueGroup));
400 int groupSize = v.mValueGroup.size();
401 for (int i = 0; i < groupSize; i++) {
402 ParamValue value = v.mValueGroup.get(i);
403 if (value.mStrValue != null) {
404 throw new ParserException("Presence Attribute value error!");
407 checkGroupValue(value.mValueGroup, UNCERTAIN_GROUP_SIZE);
408 paElem.addChild(translatePresenceAttribute(value.mValueGroup));
412 // single simple value
414 paElem.addChild(ImpsTags.PresenceValue, PtsCodes.getPAValue(v.mStrValue));
416 paElem.setContents(PtsCodes.getPAValue(v.mStrValue));
421 private static void checkGroupValue(ArrayList<ParamValue> valueGroup,
422 int expectedGroupSize) throws ParserException {
423 if (valueGroup == null
424 || (expectedGroupSize != UNCERTAIN_GROUP_SIZE
425 && valueGroup.size() != expectedGroupSize)) {
426 throw new ParserException("Invalid group value!");
429 int groupSize = valueGroup.size();
430 for (int i = 0; i < groupSize; i++) {
431 if (valueGroup.get(i) == null) {
432 throw new ParserException("Invalid group value!");
437 private static PrimitiveElement translateCapabilityList(ParamValue elemValue)
438 throws ParserException {
439 PrimitiveElement elem = new PrimitiveElement(ImpsTags.AgreedCapabilityList);
440 ArrayList<ParamValue> params = elemValue.mValueGroup;
441 if (params != null) {
442 checkGroupValue(params, UNCERTAIN_GROUP_SIZE);
443 int paramsSize = params.size();
444 for (int i = 0; i < paramsSize; i++) {
445 ArrayList<ParamValue> capElemGroup = params.get(i).mValueGroup;
446 checkGroupValue(capElemGroup, 2);
448 String capElemCode = capElemGroup.get(0).mStrValue;
450 if (capElemCode == null
451 || (capElemName = PtsCodes.getCapElement(capElemCode)) == null) {
452 throw new ParserException("Unknown capability element "
455 String capElemValue = capElemGroup.get(1).mStrValue;
456 if (capElemValue == null) {
457 throw new ParserException("Illegal capability value for "
460 capElemValue = PtsCodes.getCapValue(capElemValue);
462 elem.addChild(capElemName, capElemValue);
468 private static PrimitiveElement translateServiceTree(String elemCode,
469 ParamValue elemValue) throws ParserException {
470 String elemName = PtsCodes.getElement(elemCode);
471 PrimitiveElement elem = new PrimitiveElement(elemName);
472 // TODO: translate the service tree.
476 private static PrimitiveElement translateSimpleElem(String elemCode, ParamValue value)
477 throws ParserException {
478 String elemName = PtsCodes.getElement(elemCode);
479 if (elemName == null) {
480 throw new ParserException("Unrecognized parameter " + elemCode);
483 PrimitiveElement elem = new PrimitiveElement(elemName);
484 if (value.mStrValue != null) {
485 elem.setContents(value.mStrValue);
487 throw new ParserException("Don't know how to handle parameters for "
494 private HashMap<String, ParamValue> parseParams() throws ParserException {
496 StringBuilder buf = mStringBuf;
497 int len = buf.length();
498 HashMap<String, ParamValue> ret = new HashMap<String, ParamValue>();
501 ParamValue paramValue;
506 char ch = buf.charAt(pos);
507 if (ch == ' ' || ch == '=') {
512 if (nameStart == pos) {
513 throw new ParserException("Missing parameter name near " + pos);
515 paramName = buf.substring(nameStart, pos);
516 if (pos < len && buf.charAt(pos) == '=') {
519 paramValue = parseParamValue();
524 ret.put(paramName, paramValue);
527 // more parameters ahead
536 private ParamValue parseParamValue() throws ParserException {
538 StringBuilder buf = mStringBuf;
539 int len = buf.length();
542 throw new ParserException("Missing parameter value near " + pos);
544 ParamValue value = new ParamValue();
546 char ch = buf.charAt(pos);
550 ArrayList<ParamValue> valueGroup = new ArrayList<ParamValue>();
553 valueGroup.add(parseParamValue());
556 throw new ParserException("Unexpected parameter end");
558 if (buf.charAt(pos) != ',') {
565 if (valueGroup.isEmpty()) {
566 throw new ParserException("Empty value group near " + mPos);
568 value.mValueGroup = valueGroup;
574 StringBuilder escapedValue = new StringBuilder();
575 boolean quotedEnd = false;
577 ch = buf.charAt(pos);
580 if (pos < len && buf.charAt(pos) == '"') {
588 escapedValue.append(ch);
591 throw new ParserException("Unexpected quoted parameter end");
593 value.mStrValue = escapedValue.toString();
595 int valueStart = pos;
597 ch = buf.charAt(pos);
598 if (ch == ',' || ch == ')' || ch == ' ') {
601 if ("\"(=&".indexOf(ch) != -1) {
602 throw new ParserException("Special character " + ch
603 + " must be quoted");
607 value.mStrValue = buf.substring(valueStart, pos);
615 private void match(char c) throws ParserException {
616 if (mStringBuf.charAt(mPos) != c) {
617 throw new ParserException("Expected " + c + " at pos " + mPos);
623 * Detect if this short message is a PTS encoded WV-primitive.
625 public static boolean isPtsPrimitive(CharSequence msg)
630 Matcher m = sPreamplePattern.matcher(msg);
634 static final class ParamValue {
635 public String mStrValue;
636 public ArrayList<ParamValue> mValueGroup;