b056ca07a6a6a6f78642ab5bd6425b1ba4089cc6
[yangtools.git] / codec / yang-data-codec-binfmt / src / main / java / org / opendaylight / yangtools / yang / data / codec / binfmt / AbstractLithiumDataInput.java
1 /*
2  * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.data.codec.binfmt;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.base.Strings;
13 import com.google.common.collect.ImmutableList;
14 import com.google.common.collect.ImmutableList.Builder;
15 import com.google.common.collect.Sets;
16 import java.io.DataInput;
17 import java.io.IOException;
18 import java.io.StringReader;
19 import java.math.BigDecimal;
20 import java.math.BigInteger;
21 import java.nio.charset.StandardCharsets;
22 import java.util.ArrayList;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Set;
26 import javax.xml.parsers.DocumentBuilderFactory;
27 import javax.xml.parsers.ParserConfigurationException;
28 import javax.xml.transform.dom.DOMSource;
29 import org.opendaylight.yangtools.util.ImmutableOffsetMapTemplate;
30 import org.opendaylight.yangtools.yang.common.Empty;
31 import org.opendaylight.yangtools.yang.common.QName;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
35 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
38 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41 import org.w3c.dom.Element;
42 import org.xml.sax.InputSource;
43 import org.xml.sax.SAXException;
44
45 /**
46  * NormalizedNodeInputStreamReader reads the byte stream and constructs the normalized node including its children
47  * nodes. This process goes in recursive manner, where each NodeTypes object signifies the start of the object, except
48  * END_NODE. If a node can have children, then that node's end is calculated based on appearance of END_NODE.
49  */
50 abstract class AbstractLithiumDataInput extends AbstractNormalizedNodeDataInput {
51
52     private static final Logger LOG = LoggerFactory.getLogger(AbstractLithiumDataInput.class);
53
54     private final List<String> codedStringMap = new ArrayList<>();
55
56     private QName lastLeafSetQName;
57
58     AbstractLithiumDataInput(final DataInput input) {
59         super(input);
60     }
61
62     @Override
63     public final void streamNormalizedNode(final NormalizedNodeStreamWriter writer) throws IOException {
64         streamNormalizedNode(requireNonNull(writer), input.readByte());
65     }
66
67     private void streamNormalizedNode(final NormalizedNodeStreamWriter writer, final byte nodeType) throws IOException {
68         switch (nodeType) {
69             case LithiumNode.ANY_XML_NODE:
70                 streamAnyxml(writer);
71                 break;
72             case LithiumNode.AUGMENTATION_NODE:
73                 streamAugmentation(writer);
74                 break;
75             case LithiumNode.CHOICE_NODE:
76                 streamChoice(writer);
77                 break;
78             case LithiumNode.CONTAINER_NODE:
79                 streamContainer(writer);
80                 break;
81             case LithiumNode.LEAF_NODE:
82                 streamLeaf(writer);
83                 break;
84             case LithiumNode.LEAF_SET:
85                 streamLeafSet(writer);
86                 break;
87             case LithiumNode.ORDERED_LEAF_SET:
88                 streamOrderedLeafSet(writer);
89                 break;
90             case LithiumNode.LEAF_SET_ENTRY_NODE:
91                 streamLeafSetEntry(writer);
92                 break;
93             case LithiumNode.MAP_ENTRY_NODE:
94                 streamMapEntry(writer);
95                 break;
96             case LithiumNode.MAP_NODE:
97                 streamMap(writer);
98                 break;
99             case LithiumNode.ORDERED_MAP_NODE:
100                 streamOrderedMap(writer);
101                 break;
102             case LithiumNode.UNKEYED_LIST:
103                 streamUnkeyedList(writer);
104                 break;
105             case LithiumNode.UNKEYED_LIST_ITEM:
106                 streamUnkeyedListItem(writer);
107                 break;
108             default:
109                 throw new InvalidNormalizedNodeStreamException("Unexpected node " + nodeType);
110         }
111     }
112
113     private void streamAnyxml(final NormalizedNodeStreamWriter writer) throws IOException {
114         final NodeIdentifier identifier = readNodeIdentifier();
115         LOG.trace("Streaming anyxml node {}", identifier);
116
117         final DOMSource value = readDOMSource();
118         if (writer.startAnyxmlNode(identifier, DOMSource.class)) {
119             writer.domSourceValue(value);
120             writer.endNode();
121         }
122     }
123
124     private void streamAugmentation(final NormalizedNodeStreamWriter writer) throws IOException {
125         final AugmentationIdentifier augIdentifier = readAugmentationIdentifier();
126         LOG.trace("Streaming augmentation node {}", augIdentifier);
127         writer.startAugmentationNode(augIdentifier);
128         commonStreamContainer(writer);
129     }
130
131     private void streamChoice(final NormalizedNodeStreamWriter writer) throws IOException {
132         final NodeIdentifier identifier = readNodeIdentifier();
133         LOG.trace("Streaming choice node {}", identifier);
134         writer.startChoiceNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
135         commonStreamContainer(writer);
136     }
137
138     private void streamContainer(final NormalizedNodeStreamWriter writer) throws IOException {
139         final NodeIdentifier identifier = readNodeIdentifier();
140         LOG.trace("Streaming container node {}", identifier);
141         writer.startContainerNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
142         commonStreamContainer(writer);
143     }
144
145     private void streamLeaf(final NormalizedNodeStreamWriter writer) throws IOException {
146         startLeaf(writer);
147         endLeaf(writer, readObject());
148     }
149
150     // Leaf inside a MapEntryNode, it can potentially be a key leaf, in which case we want to de-duplicate values.
151     private void streamLeaf(final NormalizedNodeStreamWriter writer, final NodeIdentifierWithPredicates entryId)
152             throws IOException {
153         final NodeIdentifier identifier = startLeaf(writer);
154         final Object value = readObject();
155         final Object entryValue = entryId.getValue(identifier.getNodeType());
156         endLeaf(writer, entryValue == null ? value : entryValue);
157     }
158
159     private NodeIdentifier startLeaf(final NormalizedNodeStreamWriter writer) throws IOException {
160         final NodeIdentifier identifier = readNodeIdentifier();
161         LOG.trace("Streaming leaf node {}", identifier);
162         writer.startLeafNode(identifier);
163         return identifier;
164     }
165
166     private static void endLeaf(final NormalizedNodeStreamWriter writer, final Object value) throws IOException {
167         writer.scalarValue(value);
168         writer.endNode();
169     }
170
171     private void streamLeafSet(final NormalizedNodeStreamWriter writer) throws IOException {
172         final NodeIdentifier identifier = readNodeIdentifier();
173         LOG.trace("Streaming leaf set node {}", identifier);
174         writer.startLeafSet(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
175         commonStreamLeafSet(writer, identifier);
176     }
177
178     private void streamOrderedLeafSet(final NormalizedNodeStreamWriter writer) throws IOException {
179         final NodeIdentifier identifier = readNodeIdentifier();
180         LOG.trace("Streaming ordered leaf set node {}", identifier);
181         writer.startOrderedLeafSet(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
182         commonStreamLeafSet(writer, identifier);
183     }
184
185     private void commonStreamLeafSet(final NormalizedNodeStreamWriter writer, final NodeIdentifier identifier)
186             throws IOException {
187         lastLeafSetQName = identifier.getNodeType();
188         try {
189             commonStreamContainer(writer);
190         } finally {
191             // Make sure we never leak this
192             lastLeafSetQName = null;
193         }
194     }
195
196     private void streamLeafSetEntry(final NormalizedNodeStreamWriter writer) throws IOException {
197         final QName name = lastLeafSetQName != null ? lastLeafSetQName : readQName();
198         final Object value = readObject();
199         final NodeWithValue<Object> leafIdentifier = new NodeWithValue<>(name, value);
200         LOG.trace("Streaming leaf set entry node {}, value {}", leafIdentifier, value);
201         writer.startLeafSetEntryNode(leafIdentifier);
202         writer.scalarValue(value);
203         writer.endNode();
204     }
205
206     private void streamMap(final NormalizedNodeStreamWriter writer) throws IOException {
207         final NodeIdentifier identifier = readNodeIdentifier();
208         LOG.trace("Streaming map node {}", identifier);
209         writer.startMapNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
210         commonStreamContainer(writer);
211     }
212
213     private void streamOrderedMap(final NormalizedNodeStreamWriter writer) throws IOException {
214         final NodeIdentifier identifier = readNodeIdentifier();
215         LOG.trace("Streaming ordered map node {}", identifier);
216         writer.startOrderedMapNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
217         commonStreamContainer(writer);
218     }
219
220     private void streamMapEntry(final NormalizedNodeStreamWriter writer) throws IOException {
221         final NodeIdentifierWithPredicates entryIdentifier = readNormalizedNodeWithPredicates();
222         LOG.trace("Streaming map entry node {}", entryIdentifier);
223         writer.startMapEntryNode(entryIdentifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
224
225         // Same loop as commonStreamContainer(), but ...
226         for (byte nodeType = input.readByte(); nodeType != LithiumNode.END_NODE; nodeType = input.readByte()) {
227             if (nodeType == LithiumNode.LEAF_NODE) {
228                 // ... leaf nodes may need de-duplication
229                 streamLeaf(writer, entryIdentifier);
230             } else {
231                 streamNormalizedNode(writer, nodeType);
232             }
233         }
234         writer.endNode();
235     }
236
237     private void streamUnkeyedList(final NormalizedNodeStreamWriter writer) throws IOException {
238         final NodeIdentifier identifier = readNodeIdentifier();
239         LOG.trace("Streaming unkeyed list node {}", identifier);
240         writer.startUnkeyedList(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
241         commonStreamContainer(writer);
242     }
243
244     private void streamUnkeyedListItem(final NormalizedNodeStreamWriter writer) throws IOException {
245         final NodeIdentifier identifier = readNodeIdentifier();
246         LOG.trace("Streaming unkeyed list item node {}", identifier);
247         writer.startUnkeyedListItem(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
248         commonStreamContainer(writer);
249     }
250
251     private void commonStreamContainer(final NormalizedNodeStreamWriter writer) throws IOException {
252         for (byte nodeType = input.readByte(); nodeType != LithiumNode.END_NODE; nodeType = input.readByte()) {
253             streamNormalizedNode(writer, nodeType);
254         }
255         writer.endNode();
256     }
257
258     private DOMSource readDOMSource() throws IOException {
259         String xml = readObject().toString();
260         try {
261             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
262             factory.setNamespaceAware(true);
263             Element node = factory.newDocumentBuilder().parse(
264                     new InputSource(new StringReader(xml))).getDocumentElement();
265             return new DOMSource(node);
266         } catch (SAXException | ParserConfigurationException e) {
267             throw new IOException("Error parsing XML: " + xml, e);
268         }
269     }
270
271     final QName defaultReadQName() throws IOException {
272         // Read in the same sequence of writing
273         String localName = readCodedString();
274         String namespace = readCodedString();
275         String revision = Strings.emptyToNull(readCodedString());
276
277         return QNameFactory.create(localName, namespace, revision);
278     }
279
280     final String readCodedString() throws IOException {
281         final byte valueType = input.readByte();
282         switch (valueType) {
283             case LithiumTokens.IS_NULL_VALUE:
284                 return null;
285             case LithiumTokens.IS_CODE_VALUE:
286                 final int code = input.readInt();
287                 try {
288                     return codedStringMap.get(code);
289                 } catch (IndexOutOfBoundsException e) {
290                     throw new IOException("String code " + code + " was not found", e);
291                 }
292             case LithiumTokens.IS_STRING_VALUE:
293                 final String value = input.readUTF().intern();
294                 codedStringMap.add(value);
295                 return value;
296             default:
297                 throw new IOException("Unhandled string value type " + valueType);
298         }
299     }
300
301     private Set<QName> readQNameSet() throws IOException {
302         // Read the children count
303         final int count = input.readInt();
304         final Set<QName> children = Sets.newHashSetWithExpectedSize(count);
305         for (int i = 0; i < count; i++) {
306             children.add(readQName());
307         }
308         return children;
309     }
310
311     abstract AugmentationIdentifier readAugmentationIdentifier() throws IOException;
312
313     abstract NodeIdentifier readNodeIdentifier() throws IOException;
314
315     final AugmentationIdentifier defaultReadAugmentationIdentifier() throws IOException {
316         return AugmentationIdentifier.create(readQNameSet());
317     }
318
319     private NodeIdentifierWithPredicates readNormalizedNodeWithPredicates() throws IOException {
320         final QName qname = readQName();
321         final int count = input.readInt();
322         switch (count) {
323             case 0:
324                 return NodeIdentifierWithPredicates.of(qname);
325             case 1:
326                 return NodeIdentifierWithPredicates.of(qname, readQName(), readObject());
327             default:
328                 // ImmutableList is used by ImmutableOffsetMapTemplate for lookups, hence we use that.
329                 final Builder<QName> keys = ImmutableList.builderWithExpectedSize(count);
330                 final Object[] values = new Object[count];
331                 for (int i = 0; i < count; i++) {
332                     keys.add(readQName());
333                     values[i] = readObject();
334                 }
335
336                 return NodeIdentifierWithPredicates.of(qname, ImmutableOffsetMapTemplate.ordered(keys.build())
337                     .instantiateWithValues(values));
338         }
339     }
340
341     private Object readObject() throws IOException {
342         byte objectType = input.readByte();
343         switch (objectType) {
344             case LithiumValue.BITS_TYPE:
345                 return readObjSet();
346
347             case LithiumValue.BOOL_TYPE:
348                 return input.readBoolean();
349
350             case LithiumValue.BYTE_TYPE:
351                 return input.readByte();
352
353             case LithiumValue.INT_TYPE:
354                 return input.readInt();
355
356             case LithiumValue.LONG_TYPE:
357                 return input.readLong();
358
359             case LithiumValue.QNAME_TYPE:
360                 return readQName();
361
362             case LithiumValue.SHORT_TYPE:
363                 return input.readShort();
364
365             case LithiumValue.STRING_TYPE:
366                 return input.readUTF();
367
368             case LithiumValue.STRING_BYTES_TYPE:
369                 return readStringBytes();
370
371             case LithiumValue.BIG_DECIMAL_TYPE:
372                 return new BigDecimal(input.readUTF());
373
374             case LithiumValue.BIG_INTEGER_TYPE:
375                 return new BigInteger(input.readUTF());
376
377             case LithiumValue.BINARY_TYPE:
378                 byte[] bytes = new byte[input.readInt()];
379                 input.readFully(bytes);
380                 return bytes;
381
382             case LithiumValue.YANG_IDENTIFIER_TYPE:
383                 return readYangInstanceIdentifierInternal();
384
385             case LithiumValue.EMPTY_TYPE:
386             // Leaf nodes no longer allow null values and thus we no longer emit null values. Previously, the "empty"
387             // yang type was represented as null so we translate an incoming null value to Empty. It was possible for
388             // a BI user to set a string leaf to null and we're rolling the dice here but the chances for that are
389             // very low. We'd have to know the yang type but, even if we did, we can't let a null value pass upstream
390             // so we'd have to drop the leaf which might cause other issues.
391             case LithiumValue.NULL_TYPE:
392                 return Empty.value();
393
394             default:
395                 return null;
396         }
397     }
398
399     private String readStringBytes() throws IOException {
400         byte[] bytes = new byte[input.readInt()];
401         input.readFully(bytes);
402         return new String(bytes, StandardCharsets.UTF_8);
403     }
404
405     @Override
406     public final YangInstanceIdentifier readYangInstanceIdentifier() throws IOException {
407         return readYangInstanceIdentifierInternal();
408     }
409
410     private YangInstanceIdentifier readYangInstanceIdentifierInternal() throws IOException {
411         int size = input.readInt();
412         final Builder<PathArgument> pathArguments = ImmutableList.builderWithExpectedSize(size);
413         for (int i = 0; i < size; i++) {
414             pathArguments.add(readPathArgument());
415         }
416         return YangInstanceIdentifier.create(pathArguments.build());
417     }
418
419     private Set<String> readObjSet() throws IOException {
420         int count = input.readInt();
421         Set<String> children = new HashSet<>(count);
422         for (int i = 0; i < count; i++) {
423             children.add(readCodedString());
424         }
425         return children;
426     }
427
428     @Override
429     public final PathArgument readPathArgument() throws IOException {
430         // read Type
431         int type = input.readByte();
432
433         switch (type) {
434             case LithiumPathArgument.AUGMENTATION_IDENTIFIER:
435                 return readAugmentationIdentifier();
436             case LithiumPathArgument.NODE_IDENTIFIER:
437                 return readNodeIdentifier();
438             case LithiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES:
439                 return readNormalizedNodeWithPredicates();
440             case LithiumPathArgument.NODE_IDENTIFIER_WITH_VALUE:
441                 return new NodeWithValue<>(readQName(), readObject());
442             default:
443                 // FIXME: throw hard error
444                 return null;
445         }
446     }
447 }