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