Home | History | Annotate | Download | only in omadm
      1 package com.android.server.wifi.hotspot2.omadm;
      2 
      3 import org.xml.sax.Attributes;
      4 import org.xml.sax.SAXException;
      5 
      6 import java.io.IOException;
      7 import java.util.ArrayList;
      8 import java.util.Arrays;
      9 import java.util.Collections;
     10 import java.util.HashMap;
     11 import java.util.HashSet;
     12 import java.util.Iterator;
     13 import java.util.List;
     14 import java.util.Map;
     15 import java.util.Set;
     16 
     17 public class XMLNode {
     18     private final String mTag;
     19     private final Map<String, NodeAttribute> mAttributes;
     20     private final List<XMLNode> mChildren;
     21     private final XMLNode mParent;
     22     private MOTree mMO;
     23     private StringBuilder mTextBuilder;
     24     private String mText;
     25 
     26     private static final String XML_SPECIAL_CHARS = "\"'<>&";
     27     private static final Set<Character> XML_SPECIAL = new HashSet<>();
     28     private static final String CDATA_OPEN = "<![CDATA[";
     29     private static final String CDATA_CLOSE = "]]>";
     30 
     31     static {
     32         for (int n = 0; n < XML_SPECIAL_CHARS.length(); n++) {
     33             XML_SPECIAL.add(XML_SPECIAL_CHARS.charAt(n));
     34         }
     35     }
     36 
     37     public XMLNode(XMLNode parent, String tag, Attributes attributes) throws SAXException {
     38         mTag = tag;
     39 
     40         mAttributes = new HashMap<>();
     41 
     42         if (attributes.getLength() > 0) {
     43             for (int n = 0; n < attributes.getLength(); n++)
     44                 mAttributes.put(attributes.getQName(n), new NodeAttribute(attributes.getQName(n),
     45                         attributes.getType(n), attributes.getValue(n)));
     46         }
     47 
     48         mParent = parent;
     49         mChildren = new ArrayList<>();
     50 
     51         mTextBuilder = new StringBuilder();
     52     }
     53 
     54     public XMLNode(XMLNode parent, String tag, Map<String, String> attributes) {
     55         mTag = tag;
     56 
     57         mAttributes = new HashMap<>(attributes == null ? 0 : attributes.size());
     58 
     59         if (attributes != null) {
     60             for (Map.Entry<String, String> entry : attributes.entrySet()) {
     61                 mAttributes.put(entry.getKey(), new NodeAttribute(entry.getKey(), "", entry.getValue()));
     62             }
     63         }
     64 
     65         mParent = parent;
     66         mChildren = new ArrayList<>();
     67 
     68         mTextBuilder = new StringBuilder();
     69     }
     70 
     71     @Override
     72     public boolean equals(Object thatObject) {
     73         if (thatObject == this) {
     74             return true;
     75         } else if (thatObject.getClass() != XMLNode.class) {
     76             return false;
     77         }
     78 
     79         XMLNode that = (XMLNode) thatObject;
     80         if (!getTag().equals(that.getTag())
     81                 || mAttributes.size() != that.mAttributes.size()
     82                 || mChildren.size() != that.mChildren.size()) {
     83             return false;
     84         }
     85 
     86         for (Map.Entry<String, NodeAttribute> entry : mAttributes.entrySet()) {
     87             if (!entry.getValue().equals(that.mAttributes.get(entry.getKey()))) {
     88                 return false;
     89             }
     90         }
     91 
     92         List<XMLNode> cloneOfThat = new ArrayList<>(that.mChildren);
     93         for (XMLNode child : mChildren) {
     94             Iterator<XMLNode> thatChildren = cloneOfThat.iterator();
     95             boolean found = false;
     96             while (thatChildren.hasNext()) {
     97                 XMLNode thatChild = thatChildren.next();
     98                 if (child.equals(thatChild)) {
     99                     found = true;
    100                     thatChildren.remove();
    101                     break;
    102                 }
    103             }
    104             if (!found) {
    105                 return false;
    106             }
    107         }
    108         return true;
    109     }
    110 
    111     public void setText(String text) {
    112         mText = text;
    113         mTextBuilder = null;
    114     }
    115 
    116     public void addText(char[] chs, int start, int length) {
    117         String s = new String(chs, start, length);
    118         String trimmed = s.trim();
    119         if (trimmed.isEmpty())
    120             return;
    121 
    122         if (s.charAt(0) != trimmed.charAt(0))
    123             mTextBuilder.append(' ');
    124         mTextBuilder.append(trimmed);
    125         if (s.charAt(s.length() - 1) != trimmed.charAt(trimmed.length() - 1))
    126             mTextBuilder.append(' ');
    127     }
    128 
    129     public void addChild(XMLNode child) {
    130         mChildren.add(child);
    131     }
    132 
    133     public void close() throws IOException, SAXException {
    134         String text = mTextBuilder.toString().trim();
    135         StringBuilder filtered = new StringBuilder(text.length());
    136         for (int n = 0; n < text.length(); n++) {
    137             char ch = text.charAt(n);
    138             if (ch >= ' ')
    139                 filtered.append(ch);
    140         }
    141 
    142         mText = filtered.toString();
    143         mTextBuilder = null;
    144 
    145         if (MOTree.hasMgmtTreeTag(mText)) {
    146             try {
    147                 NodeAttribute urn = mAttributes.get(OMAConstants.SppMOAttribute);
    148                 OMAParser omaParser = new OMAParser();
    149                 mMO = omaParser.parse(mText, urn != null ? urn.getValue() : null);
    150             }
    151             catch (SAXException | IOException e) {
    152                 mMO = null;
    153             }
    154         }
    155     }
    156 
    157     public String getTag() {
    158         return mTag;
    159     }
    160 
    161     public String getNameSpace() throws OMAException {
    162         String[] nsn = mTag.split(":");
    163         if (nsn.length != 2) {
    164             throw new OMAException("Non-namespaced tag: '" + mTag + "'");
    165         }
    166         return nsn[0];
    167     }
    168 
    169     public String getStrippedTag() throws OMAException {
    170         String[] nsn = mTag.split(":");
    171         if (nsn.length != 2) {
    172             throw new OMAException("Non-namespaced tag: '" + mTag + "'");
    173         }
    174         return nsn[1].toLowerCase();
    175     }
    176 
    177     public XMLNode getSoleChild() throws OMAException{
    178         if (mChildren.size() != 1) {
    179             throw new OMAException("Expected exactly one child to " + mTag);
    180         }
    181         return mChildren.get(0);
    182     }
    183 
    184     public XMLNode getParent() {
    185         return mParent;
    186     }
    187 
    188     public String getText() {
    189         return mText;
    190     }
    191 
    192     public Map<String, NodeAttribute> getAttributes() {
    193         return Collections.unmodifiableMap(mAttributes);
    194     }
    195 
    196     /**
    197      * Get the attributes of this node as a map of attribute name to attribute value.
    198      * @return The attribute mapping.
    199      */
    200     public Map<String, String> getTextualAttributes() {
    201         Map<String, String> map = new HashMap<>(mAttributes.size());
    202         for (Map.Entry<String, NodeAttribute> entry : mAttributes.entrySet()) {
    203             map.put(entry.getKey(), entry.getValue().getValue());
    204         }
    205         return map;
    206     }
    207 
    208     public String getAttributeValue(String name) {
    209         NodeAttribute nodeAttribute = mAttributes.get(name);
    210         return nodeAttribute != null ? nodeAttribute.getValue() : null;
    211     }
    212 
    213     public List<XMLNode> getChildren() {
    214         return mChildren;
    215     }
    216 
    217     public MOTree getMOTree() {
    218         return mMO;
    219     }
    220 
    221     private void toString(char[] indent, StringBuilder sb) {
    222         Arrays.fill(indent, ' ');
    223 
    224         sb.append(indent).append('<').append(mTag);
    225         for (Map.Entry<String, NodeAttribute> entry : mAttributes.entrySet()) {
    226             sb.append(' ').append(entry.getKey()).append("='").append(entry.getValue().getValue()).append('\'');
    227         }
    228 
    229         if (mText != null && !mText.isEmpty()) {
    230             sb.append('>').append(escapeCdata(mText)).append("</").append(mTag).append(">\n");
    231         }
    232         else if (mChildren.isEmpty()) {
    233             sb.append("/>\n");
    234         }
    235         else {
    236             sb.append(">\n");
    237             char[] subIndent = Arrays.copyOf(indent, indent.length + 2);
    238             for (XMLNode child : mChildren) {
    239                 child.toString(subIndent, sb);
    240             }
    241             sb.append(indent).append("</").append(mTag).append(">\n");
    242         }
    243     }
    244 
    245     private static String escapeCdata(String text) {
    246         if (!escapable(text)) {
    247             return text;
    248         }
    249 
    250         // Any appearance of ]]> in the text must be split into "]]" | "]]>" | <![CDATA[ | ">"
    251         // i.e. "split the sequence by putting a close CDATA and a new open CDATA before the '>'
    252         StringBuilder sb = new StringBuilder();
    253         sb.append(CDATA_OPEN);
    254         int start = 0;
    255         for (;;) {
    256             int etoken = text.indexOf(CDATA_CLOSE);
    257             if (etoken >= 0) {
    258                 sb.append(text.substring(start, etoken + 2)).append(CDATA_CLOSE).append(CDATA_OPEN);
    259                 start = etoken + 2;
    260             }
    261             else {
    262                 if (start < text.length() - 1) {
    263                     sb.append(text.substring(start));
    264                 }
    265                 break;
    266             }
    267         }
    268         sb.append(CDATA_CLOSE);
    269         return sb.toString();
    270     }
    271 
    272     private static boolean escapable(String s) {
    273         for (int n = 0; n < s.length(); n++) {
    274             if (XML_SPECIAL.contains(s.charAt(n))) {
    275                 return true;
    276             }
    277         }
    278         return false;
    279     }
    280 
    281     @Override
    282     public String toString() {
    283         StringBuilder sb = new StringBuilder();
    284         toString(new char[0], sb);
    285         return sb.toString();
    286     }
    287 }
    288