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