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