MOTree.java revision c9b88562541a0c2acd60d0a01ac1e182e73c79f9
1package com.android.server.wifi.hotspot2.omadm; 2 3import org.xml.sax.SAXException; 4 5import java.io.FileNotFoundException; 6import java.io.IOException; 7import java.io.InputStream; 8import java.io.OutputStream; 9import java.nio.charset.StandardCharsets; 10import java.util.*; 11 12public class MOTree { 13 public static final String MgmtTreeTag = "MgmtTree"; 14 15 public static final String NodeTag = "Node"; 16 public static final String NodeNameTag = "NodeName"; 17 public static final String PathTag = "Path"; 18 public static final String ValueTag = "Value"; 19 public static final String RTPropTag = "RTProperties"; 20 public static final String TypeTag = "Type"; 21 public static final String DDFNameTag = "DDFName"; 22 23 private final String mUrn; 24 private final String mDtdRev; 25 private final OMAConstructed mRoot; 26 27 public MOTree(XMLNode node, String urn) throws IOException, SAXException { 28 Iterator<XMLNode> children = node.getChildren().iterator(); 29 30 String dtdRev = null; 31 32 while (children.hasNext()) { 33 XMLNode child = children.next(); 34 if (child.getTag().equals(OMAConstants.SyncMLVersionTag)) { 35 dtdRev = child.getText(); 36 children.remove(); 37 break; 38 } 39 } 40 41 mUrn = urn; 42 mDtdRev = dtdRev; 43 44 mRoot = new ManagementTreeRoot(node, dtdRev); 45 46 for (XMLNode child : node.getChildren()) { 47 buildNode(mRoot, child); 48 } 49 } 50 51 public MOTree(String urn, String rev, OMAConstructed root) { 52 mUrn = urn; 53 mDtdRev = rev; 54 mRoot = root; 55 } 56 57 /** 58 * Build a Passpoint OMA-DM Management Object tree object. 59 * @param urn The URN for the tree. 60 * @param rev The DTD revision for the tree. 61 * @param root The OMA-DM tree root, in all practical cases the PerProviderSubscription 62 * node. 63 * @return an MOTree object 64 */ 65 public static MOTree buildMgmtTree(String urn, String rev, OMAConstructed root) { 66 OMAConstructed realRoot; 67 switch (urn) { 68 case OMAConstants.PPS_URN: 69 case OMAConstants.DevInfoURN: 70 case OMAConstants.DevDetailURN: 71 case OMAConstants.DevDetailXURN: 72 realRoot = new ManagementTreeRoot(OMAConstants.OMAVersion); 73 realRoot.addChild(root); 74 return new MOTree(urn, rev, realRoot); 75 default: 76 return new MOTree(urn, rev, root); 77 } 78 } 79 80 public static boolean hasMgmtTreeTag(String text) { 81 for (int n = 0; n < text.length(); n++) { 82 char ch = text.charAt(n); 83 if (ch > ' ') { 84 return text.regionMatches(true, n, '<' + MgmtTreeTag + '>', 85 0, MgmtTreeTag.length() + 2); 86 } 87 } 88 return false; 89 } 90 91 private static class NodeData { 92 private final String mName; 93 private String mPath; 94 private String mValue; 95 96 private NodeData(String name) { 97 mName = name; 98 } 99 100 private void setPath(String path) { 101 mPath = path; 102 } 103 104 private void setValue(String value) { 105 mValue = value; 106 } 107 108 public String getName() { 109 return mName; 110 } 111 112 public String getPath() { 113 return mPath; 114 } 115 116 public String getValue() { 117 return mValue; 118 } 119 } 120 121 private static void buildNode(OMANode parent, XMLNode node) throws IOException { 122 if (!node.getTag().equals(NodeTag)) 123 throw new IOException("Node is a '" + node.getTag() + "' instead of a 'Node'"); 124 125 Map<String, XMLNode> checkMap = new HashMap<>(3); 126 String context = null; 127 List<NodeData> values = new ArrayList<>(); 128 List<XMLNode> children = new ArrayList<>(); 129 130 NodeData curValue = null; 131 132 for (XMLNode child : node.getChildren()) { 133 XMLNode old = checkMap.put(child.getTag(), child); 134 135 switch (child.getTag()) { 136 case NodeNameTag: 137 if (curValue != null) 138 throw new IOException(NodeNameTag + " not expected"); 139 curValue = new NodeData(child.getText()); 140 141 break; 142 case PathTag: 143 if (curValue == null || curValue.getPath() != null) 144 throw new IOException(PathTag + " not expected"); 145 curValue.setPath(child.getText()); 146 147 break; 148 case ValueTag: 149 if (!children.isEmpty()) 150 throw new IOException(ValueTag + " in constructed node"); 151 if (curValue == null || curValue.getValue() != null) 152 throw new IOException(ValueTag + " not expected"); 153 curValue.setValue(child.getText()); 154 values.add(curValue); 155 curValue = null; 156 157 break; 158 case RTPropTag: 159 if (old != null) 160 throw new IOException("Duplicate " + RTPropTag); 161 XMLNode typeNode = getNextNode(child, TypeTag); 162 XMLNode ddfName = getNextNode(typeNode, DDFNameTag); 163 context = ddfName.getText(); 164 if (context == null) 165 throw new IOException("No text in " + DDFNameTag); 166 167 break; 168 case NodeTag: 169 if (!values.isEmpty()) 170 throw new IOException("Scalar node " + node.getText() + " has Node child"); 171 children.add(child); 172 173 break; 174 } 175 } 176 177 if (values.isEmpty()) { 178 if (curValue == null) 179 throw new IOException("Missing name"); 180 181 OMANode subNode = parent.addChild(curValue.getName(), 182 context, null, curValue.getPath()); 183 184 for (XMLNode child : children) { 185 buildNode(subNode, child); 186 } 187 } else { 188 if (!children.isEmpty()) 189 throw new IOException("Got both sub nodes and value(s)"); 190 191 for (NodeData nodeData : values) { 192 parent.addChild(nodeData.getName(), context, 193 nodeData.getValue(), nodeData.getPath()); 194 } 195 } 196 } 197 198 private static XMLNode getNextNode(XMLNode node, String tag) throws IOException { 199 if (node == null) 200 throw new IOException("No node for " + tag); 201 if (node.getChildren().size() != 1) 202 throw new IOException("Expected " + node.getTag() + " to have exactly one child"); 203 XMLNode child = node.getChildren().iterator().next(); 204 if (!child.getTag().equals(tag)) 205 throw new IOException("Expected " + node.getTag() + " to have child '" + tag + 206 "' instead of '" + child.getTag() + "'"); 207 return child; 208 } 209 210 public String getUrn() { 211 return mUrn; 212 } 213 214 public String getDtdRev() { 215 return mDtdRev; 216 } 217 218 public OMAConstructed getRoot() { 219 return mRoot; 220 } 221 222 @Override 223 public String toString() { 224 StringBuilder sb = new StringBuilder(); 225 sb.append("MO Tree v").append(mDtdRev).append(", urn ").append(mUrn).append(")\n"); 226 sb.append(mRoot); 227 228 return sb.toString(); 229 } 230 231 public void marshal(OutputStream out) throws IOException { 232 out.write("tree ".getBytes(StandardCharsets.UTF_8)); 233 OMAConstants.serializeString(mDtdRev, out); 234 out.write(String.format("(%s)\n", mUrn).getBytes(StandardCharsets.UTF_8)); 235 mRoot.marshal(out, 0); 236 } 237 238 public static MOTree unmarshal(InputStream in) throws IOException { 239 boolean strip = true; 240 StringBuilder tree = new StringBuilder(); 241 for (; ; ) { 242 int octet = in.read(); 243 if (octet < 0) { 244 throw new FileNotFoundException(); 245 } else if (octet > ' ') { 246 tree.append((char) octet); 247 strip = false; 248 } else if (!strip) { 249 break; 250 } 251 } 252 if (!tree.toString().equals("tree")) { 253 throw new IOException("Not a tree: " + tree); 254 } 255 256 String version = OMAConstants.deserializeString(in); 257 int next = in.read(); 258 if (next != '(') { 259 throw new IOException("Expected URN in tree definition"); 260 } 261 String urn = OMAConstants.readURN(in); 262 263 OMAConstructed root = OMANode.unmarshal(in); 264 265 return new MOTree(urn, version, root); 266 } 267 268 public String toXml() { 269 StringBuilder sb = new StringBuilder(); 270 mRoot.toXml(sb); 271 return sb.toString(); 272 } 273} 274