blob: 1e9982afdcbccc1dab83ce818e0e1bc5c00326c9 [file] [log] [blame]
/*
* Copyright (C) 2007-2008 Esmertec AG.
* Copyright (C) 2007-2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.im.imps;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.android.im.imps.ImpsConstants.ImpsVersion;
public class PtsPrimitiveSerializer implements PrimitiveSerializer {
private final String mPreampleHead;
// The ccc is the Transaction-ID in range 0-999 without preceding zero.
private static final Pattern sTxIdPattern = Pattern.compile("(0|[1-9]\\d{0,2})");
// If the value of the parameter contains spaces ( ), quotes ("),
// commas (,),parentheses (()),equal (=) or ampersand (&) characters,
// it SHALL be wrapped with quotes (").
private static final Pattern sCharsToBeQuoted = Pattern.compile("[ \",\\(\\)=&]");
public PtsPrimitiveSerializer(ImpsVersion impsVersion) throws SerializerException {
if (impsVersion == ImpsVersion.IMPS_VERSION_11) {
mPreampleHead = "WV11";
}else if (impsVersion == ImpsVersion.IMPS_VERSION_12) {
mPreampleHead = "WV12";
} else if (impsVersion == ImpsVersion.IMPS_VERSION_13) {
mPreampleHead = "WV13";
} else {
throw new SerializerException("Unsupported IMPS version");
}
}
public void serialize(Primitive p, OutputStream out)
throws IOException, SerializerException {
String txId = p.getTransactionID();
if (txId == null) {
if (!ImpsTags.Polling_Request.equals(p.getType())) {
throw new SerializerException("null Transaction-ID for non polling request");
}
// FIXME: what should this be? Temporarily use 0
txId = "0";
} else {
Matcher m = sTxIdPattern.matcher(txId);
if (!m.matches()) {
throw new SerializerException(
"Transaction-ID must be in range 0-999 without preceding zero");
}
}
// TODO: use buffered writer?
Writer writer = new OutputStreamWriter(out, "UTF-8");
writer.write(mPreampleHead);
String code = PtsCodes.getTxCode(p.getType());
if (code == null) {
throw new SerializerException("Unsupported transaction type "
+ p.getType());
}
writer.write(code);
writer.write(txId);
if (p.getSessionId() != null) {
writer.write(" SI=");
writer.write(p.getSessionId());
}
PrimitiveElement content = p.getContentElement();
if (content != null && content.getChildCount() > 0) {
ArrayList<PrimitiveElement> infoElems = content.getChildren();
ArrayList<String> users = new ArrayList<String>();
ArrayList<String> lists = new ArrayList<String>();
int len = infoElems.size();
for (int i = 0; i < len; i++) {
PrimitiveElement elem = infoElems.get(i);
String elemName = elem.getTagName();
// workaround for multiple elements
if (ImpsTags.User.equals(elemName)) {
users.add(elem.getChildContents(ImpsTags.UserID));
continue;
} else if (ImpsTags.UserID.equals(elemName)) {
users.add(elem.getContents());
continue;
} else if (ImpsTags.ContactList.equals(elemName)) {
lists.add(elem.getContents());
continue;
}
String elemCode = PtsCodes.getElementCode(elemName, p.getType());
if (elemCode == null) {
throw new SerializerException("Don't know how to encode element "
+ elemName);
}
writer.write(' ');
writer.write(elemCode);
// so far all top level information elements have values.
writer.write('=');
String value;
ElemValueEncoder encoder = ElemValueEncoder.getEncoder(elemName);
if (encoder == null) {
// default simple value
value = escapeValueString(elem.getContents());
} else {
value = encoder.encodeValue(p, elem);
}
if (value == null) {
throw new SerializerException("Empty value for element "
+ elemName);
}
writer.write(value);
}
writeMultiValue(writer, PtsCodes.getElementCode(ImpsTags.UserID, p.getType()), users);
writeMultiValue(writer, PtsCodes.getElementCode(ImpsTags.ContactList, p.getType()), lists);
}
writer.close();
}
private void writeMultiValue(Writer writer, String code, ArrayList<String> values)
throws IOException {
if (values.size() == 0) {
return;
}
writer.write(' ');
writer.write(code);
writer.write('=');
if (values.size() == 1) {
writer.write(escapeValueString(values.get(0)));
} else {
writer.write('(');
int valueCount = values.size();
for (int i = 0; i < valueCount; i++) {
if (i > 0) {
writer.write(',');
}
writer.write(escapeValueString(values.get(i)));
}
writer.write(')');
}
}
static String escapeValueString(String contents) {
Matcher m = sCharsToBeQuoted.matcher(contents);
if (m.find()) {
if (contents.indexOf('"') != -1) {
contents = contents.replace("\"", "\"\"");
}
return "\"" + contents + "\"";
}
return contents;
}
static void appendPairValue(StringBuilder buf, String first, String second) {
buf.append('(');
if (first != null) {
buf.append(first);
}
buf.append(',');
buf.append(second);
buf.append(')');
}
/**
* Appends a name and value pair like "(<name>,<value>)".
*/
static boolean appendNameAndValue(StringBuilder buf, String name, String value,
HashMap<String, String> nameCodes, HashMap<String, String> valueCodes,
boolean ignoreUnsupportedValue) {
String nameCode = nameCodes.get(name);
if (nameCode == null) {
ImpsLog.log("PTS: Ignoring value " + name);
return false;
}
String valueCode = null;
if (valueCodes != null) {
valueCode = valueCodes.get(value);
}
if (valueCode != null) {
value = valueCode;
} else {
if (ignoreUnsupportedValue) {
return false;
}
value = escapeValueString(value);
}
appendPairValue(buf, nameCode, value);
return true;
}
static abstract class ElemValueEncoder {
public abstract String encodeValue(Primitive p, PrimitiveElement elem)
throws SerializerException;
public static ElemValueEncoder getEncoder(String elemName) {
return sEncoders.get(elemName);
}
private static HashMap<String, ElemValueEncoder> sEncoders;
static {
sEncoders = new HashMap<String, ElemValueEncoder>();
sEncoders.put(ImpsTags.ClientID, new ClientIdEncoder());
sEncoders.put(ImpsTags.CapabilityList, new CapabilityEncoder());
sEncoders.put(ImpsTags.Functions, new ServiceTreeEncoder());
sEncoders.put(ImpsTags.Result, new ResultEncoder());
sEncoders.put(ImpsTags.ContactListProperties, new ProperitiesEncoder(
PtsCodes.sContactListPropsToCode));
sEncoders.put(ImpsTags.PresenceSubList, new PresenceSubListEncoder());
ElemValueEncoder nickListEncoder = new NickListEncoder();
sEncoders.put(ImpsTags.NickList, nickListEncoder);
sEncoders.put(ImpsTags.AddNickList, nickListEncoder);
sEncoders.put(ImpsTags.RemoveNickList, nickListEncoder);
}
}
static class PresenceSubListEncoder extends ElemValueEncoder {
private boolean mEncodePresenceValue;
@Override
public String encodeValue(Primitive p, PrimitiveElement elem)
throws SerializerException {
if (elem.getChildCount() == 0) {
throw new SerializerException("No presence in the PresenceSubList");
}
StringBuilder buf = new StringBuilder();
mEncodePresenceValue = ImpsTags.UpdatePresence_Request.equals(p.getType());
ArrayList<PrimitiveElement> presences = elem.getChildren();
int presenceCount = presences.size();
if (presenceCount == 1) {
if (mEncodePresenceValue) {
// Append an extra pair of braces according to the Spec
buf.append('(');
encodePresence(buf, presences.get(0));
buf.append(')');
} else {
encodePresence(buf, presences.get(0));
}
} else {
buf.append('(');
for (int i = 0; i < presenceCount; i++) {
if (i > 0) {
buf.append(',');
}
encodePresence(buf, presences.get(i));
}
buf.append(')');
}
return buf.toString();
}
private void encodePresence(StringBuilder buf, PrimitiveElement p)
throws SerializerException {
boolean hasQualifier = p.getChild(ImpsTags.Qualifier) != null;
String presenceName = p.getTagName();
String presenceNameCode = getPresenceCode(presenceName);
if (!mEncodePresenceValue) {
encodeNoValuePresence(buf, p);
} else {
buf.append('(');
buf.append(presenceNameCode);
buf.append(',');
if (hasQualifier) {
buf.append(p.getChildContents(ImpsTags.Qualifier));
buf.append(',');
}
// All the presences with value have this kind of structure:
// <name, qualifier, value>
// And for the values, there are three different hierarchies:
// 1. Simply use PresenceValue to indicate the value, most of the
// presences has adapted this way. -> SingleValue
// 2. Use special tags for multiple values of this presence, eg. ClientInfo
// has adapted this way. -> MultiValue
// 3. Has one or more children for the presence, and each child have
// multiple values. eg. CommCap has adapted this way. -> ExtMultiValue
if (isMultiValuePresence(presenceName)) {
// condition 2: multiple value
int emptyValueSize = hasQualifier ? 1 : 0;
ArrayList<PrimitiveElement> children = p.getChildren();
if (children.size() > emptyValueSize) {
buf.append('(');
int childCount = children.size();
int j = 0; // used for first value check
for (int i = 0; i < childCount; i++, j++) {
PrimitiveElement value = children.get(i);
if (ImpsTags.Qualifier.equals(value.getTagName())) {
j--;
continue;
}
if (j > 0) {
buf.append(',');
}
buf.append('(');
buf.append(getPresenceCode(value.getTagName()));
buf.append(',');
buf.append(PtsCodes.getPAValueCode(value.getContents()));
buf.append(')');
}
buf.append(')');
}
} else if (isExtMultiValuePresence(presenceName)) {
// condition 3: extended multiple value
// TODO: Implementation
} else {
// Condition 1: single value
if (p.getChild(ImpsTags.PresenceValue) == null) {
throw new SerializerException("Can't find presence value for " + presenceName);
}
buf.append(PtsCodes.getPAValueCode(p.getChildContents(ImpsTags.PresenceValue)));
}
buf.append(')');
}
}
private void encodeNoValuePresence(StringBuilder buf, PrimitiveElement p)
throws SerializerException {
if (p.getChildCount() == 0) {
buf.append(getPresenceCode(p.getTagName()));
} else {
ArrayList<PrimitiveElement> children = p.getChildren();
int childCount = children.size();
buf.append('(');
buf.append(getPresenceCode(p.getTagName()));
buf.append(",(");
for (int i = 0; i < childCount; i++) {
if (i > 0) {
buf.append(',');
}
encodeNoValuePresence(buf, children.get(i));
}
buf.append("))");
}
}
private String getPresenceCode(String tagname) throws SerializerException {
String code = PtsCodes.getPresenceAttributeCode(tagname);
if (code == null) {
throw new SerializerException("Unsupport presence attribute: " + tagname);
}
return code;
}
private boolean isMultiValuePresence(String presenceName) {
if (ImpsTags.ClientInfo.equals(presenceName)) {
return true;
}
// TODO: Add more supported extended multiple presence here
return false;
}
private boolean isExtMultiValuePresence(String presenceName) {
// TODO: Add supported extended multiple presence here
return false;
}
}
static class ClientIdEncoder extends ElemValueEncoder {
@Override
public String encodeValue(Primitive p, PrimitiveElement elem)
throws SerializerException {
String value = elem.getChildContents(ImpsTags.URL);
if (value == null) {
value = elem.getChildContents(ImpsTags.MSISDN);
}
return escapeValueString(value);
}
}
static class CapabilityEncoder extends ElemValueEncoder {
@Override
public String encodeValue(Primitive p, PrimitiveElement elem)
throws SerializerException {
ArrayList<PrimitiveElement> caps = elem.getChildren();
int i, len;
StringBuilder result = new StringBuilder();
result.append('(');
for (i = 0, len = caps.size(); i < len; i++) {
PrimitiveElement capElem = caps.get(i);
String capName = capElem.getTagName();
String capValue = capElem.getContents();
if (i > 0) {
result.append(',');
}
if (!appendNameAndValue(result, capName, capValue,
PtsCodes.sCapElementToCode, PtsCodes.sCapValueToCode,
ImpsTags.SupportedCIRMethod.equals(capName))) {
result.deleteCharAt(result.length() - 1);
}
}
result.append(')');
return result.toString();
}
}
static class ServiceTreeEncoder extends ElemValueEncoder {
@Override
public String encodeValue(Primitive p, PrimitiveElement elem)
throws SerializerException {
StringBuilder buf = new StringBuilder();
buf.append('(');
appendFeature(buf, elem.getFirstChild());
buf.append(')');
return buf.toString();
}
private void appendFeature(StringBuilder buf, PrimitiveElement elem)
throws SerializerException {
int childCount = elem.getChildCount();
if (childCount > 0) {
ArrayList<PrimitiveElement> children = elem.getChildren();
for (int i = 0; i < childCount; i++) {
appendFeature(buf, children.get(i));
}
} else {
String code = PtsCodes.getServiceTreeCode(elem.getTagName());
if (code == null) {
throw new SerializerException("Invalid service tree tag:"
+ elem.getTagName());
}
if (buf.length() > 1) {
buf.append(',');
}
buf.append(code);
}
}
}
static class ResultEncoder extends ElemValueEncoder {
@Override
public String encodeValue(Primitive p, PrimitiveElement elem)
throws SerializerException {
String code = elem.getChildContents(ImpsTags.Code);
String desc = elem.getChildContents(ImpsTags.Description);
// Client never sends partial success result, the DetailedResult is
// ignored.
if (desc == null) {
return code;
} else {
StringBuilder res = new StringBuilder();
appendPairValue(res, code, escapeValueString(desc));
return res.toString();
}
}
}
static class NickListEncoder extends ElemValueEncoder {
@Override
public String encodeValue(Primitive p, PrimitiveElement elem)
throws SerializerException {
StringBuilder buf = new StringBuilder();
ArrayList<PrimitiveElement> children = elem.getChildren();
int count = children.size();
buf.append('(');
for (int i = 0; i < count; i++) {
PrimitiveElement child = children.get(i);
String tagName = child.getTagName();
String nickName = null;
String userId = null;
if (tagName.equals(ImpsTags.NickName)) {
nickName = child.getChildContents(ImpsTags.Name);
userId = child.getChildContents(ImpsTags.UserID);
} else if (tagName.equals(ImpsTags.UserID)) {
userId = child.getContents();
}
if (i > 0) {
buf.append(',');
}
if (nickName != null) {
nickName = escapeValueString(nickName);
}
appendPairValue(buf, nickName, escapeValueString(userId));
}
buf.append(')');
return buf.toString();
}
}
static class ProperitiesEncoder extends ElemValueEncoder {
private HashMap<String, String> mPropNameCodes;
public ProperitiesEncoder(HashMap<String, String> propNameCodes) {
mPropNameCodes = propNameCodes;
}
@Override
public String encodeValue(Primitive p, PrimitiveElement elem)
throws SerializerException {
ArrayList<PrimitiveElement> props = elem.getChildren();
StringBuilder result = new StringBuilder();
result.append('(');
int count = props.size();
for (int i = 0; i < count; i++) {
PrimitiveElement property = props.get(i);
String name;
String value;
if (property.getTagName().equals(ImpsTags.Property)) {
name = property.getChildContents(ImpsTags.Name);
value = property.getChildContents(ImpsTags.Value);
} else {
name = property.getTagName();
value = property.getContents();
}
if (i > 0) {
result.append(',');
}
appendNameAndValue(result, name, value, mPropNameCodes, null, false);
}
result.append(')');
return result.toString();
}
}
}