2 * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.data.codec.binfmt;
10 import static java.util.Objects.requireNonNull;
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;
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;
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.
50 abstract class AbstractLithiumDataInput extends AbstractNormalizedNodeDataInput {
52 private static final Logger LOG = LoggerFactory.getLogger(AbstractLithiumDataInput.class);
54 private final List<String> codedStringMap = new ArrayList<>();
56 private QName lastLeafSetQName;
58 AbstractLithiumDataInput(final DataInput input) {
63 public final void streamNormalizedNode(final NormalizedNodeStreamWriter writer) throws IOException {
64 streamNormalizedNode(requireNonNull(writer), input.readByte());
67 private void streamNormalizedNode(final NormalizedNodeStreamWriter writer, final byte nodeType) throws IOException {
69 case LithiumNode.ANY_XML_NODE:
72 case LithiumNode.AUGMENTATION_NODE:
73 streamAugmentation(writer);
75 case LithiumNode.CHOICE_NODE:
78 case LithiumNode.CONTAINER_NODE:
79 streamContainer(writer);
81 case LithiumNode.LEAF_NODE:
84 case LithiumNode.LEAF_SET:
85 streamLeafSet(writer);
87 case LithiumNode.ORDERED_LEAF_SET:
88 streamOrderedLeafSet(writer);
90 case LithiumNode.LEAF_SET_ENTRY_NODE:
91 streamLeafSetEntry(writer);
93 case LithiumNode.MAP_ENTRY_NODE:
94 streamMapEntry(writer);
96 case LithiumNode.MAP_NODE:
99 case LithiumNode.ORDERED_MAP_NODE:
100 streamOrderedMap(writer);
102 case LithiumNode.UNKEYED_LIST:
103 streamUnkeyedList(writer);
105 case LithiumNode.UNKEYED_LIST_ITEM:
106 streamUnkeyedListItem(writer);
109 throw new InvalidNormalizedNodeStreamException("Unexpected node " + nodeType);
113 private void streamAnyxml(final NormalizedNodeStreamWriter writer) throws IOException {
114 final NodeIdentifier identifier = readNodeIdentifier();
115 LOG.trace("Streaming anyxml node {}", identifier);
117 final DOMSource value = readDOMSource();
118 if (writer.startAnyxmlNode(identifier, DOMSource.class)) {
119 writer.domSourceValue(value);
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);
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);
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);
145 private void streamLeaf(final NormalizedNodeStreamWriter writer) throws IOException {
147 endLeaf(writer, readObject());
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)
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);
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);
166 private static void endLeaf(final NormalizedNodeStreamWriter writer, final Object value) throws IOException {
167 writer.scalarValue(value);
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);
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);
185 private void commonStreamLeafSet(final NormalizedNodeStreamWriter writer, final NodeIdentifier identifier)
187 lastLeafSetQName = identifier.getNodeType();
189 commonStreamContainer(writer);
191 // Make sure we never leak this
192 lastLeafSetQName = null;
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);
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);
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);
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);
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);
231 streamNormalizedNode(writer, nodeType);
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);
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);
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);
258 private DOMSource readDOMSource() throws IOException {
259 String xml = readObject().toString();
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);
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());
277 return QNameFactory.create(localName, namespace, revision);
280 final String readCodedString() throws IOException {
281 final byte valueType = input.readByte();
283 case LithiumTokens.IS_NULL_VALUE:
285 case LithiumTokens.IS_CODE_VALUE:
286 final int code = input.readInt();
288 return codedStringMap.get(code);
289 } catch (IndexOutOfBoundsException e) {
290 throw new IOException("String code " + code + " was not found", e);
292 case LithiumTokens.IS_STRING_VALUE:
293 final String value = input.readUTF().intern();
294 codedStringMap.add(value);
297 throw new IOException("Unhandled string value type " + valueType);
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());
311 abstract AugmentationIdentifier readAugmentationIdentifier() throws IOException;
313 abstract NodeIdentifier readNodeIdentifier() throws IOException;
315 final AugmentationIdentifier defaultReadAugmentationIdentifier() throws IOException {
316 return AugmentationIdentifier.create(readQNameSet());
319 private NodeIdentifierWithPredicates readNormalizedNodeWithPredicates() throws IOException {
320 final QName qname = readQName();
321 final int count = input.readInt();
324 return NodeIdentifierWithPredicates.of(qname);
326 return NodeIdentifierWithPredicates.of(qname, readQName(), readObject());
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();
336 return NodeIdentifierWithPredicates.of(qname, ImmutableOffsetMapTemplate.ordered(keys.build())
337 .instantiateWithValues(values));
341 private Object readObject() throws IOException {
342 byte objectType = input.readByte();
343 switch (objectType) {
344 case LithiumValue.BITS_TYPE:
347 case LithiumValue.BOOL_TYPE:
348 return input.readBoolean();
350 case LithiumValue.BYTE_TYPE:
351 return input.readByte();
353 case LithiumValue.INT_TYPE:
354 return input.readInt();
356 case LithiumValue.LONG_TYPE:
357 return input.readLong();
359 case LithiumValue.QNAME_TYPE:
362 case LithiumValue.SHORT_TYPE:
363 return input.readShort();
365 case LithiumValue.STRING_TYPE:
366 return input.readUTF();
368 case LithiumValue.STRING_BYTES_TYPE:
369 return readStringBytes();
371 case LithiumValue.BIG_DECIMAL_TYPE:
372 return new BigDecimal(input.readUTF());
374 case LithiumValue.BIG_INTEGER_TYPE:
375 return new BigInteger(input.readUTF());
377 case LithiumValue.BINARY_TYPE:
378 byte[] bytes = new byte[input.readInt()];
379 input.readFully(bytes);
382 case LithiumValue.YANG_IDENTIFIER_TYPE:
383 return readYangInstanceIdentifierInternal();
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();
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);
406 public final YangInstanceIdentifier readYangInstanceIdentifier() throws IOException {
407 return readYangInstanceIdentifierInternal();
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());
416 return YangInstanceIdentifier.create(pathArguments.build());
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());
429 public final PathArgument readPathArgument() throws IOException {
431 int type = input.readByte();
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());
443 // FIXME: throw hard error