/* * 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(); } } }