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.ImmutableSet;
16 import java.io.DataInput;
17 import java.io.IOException;
18 import java.io.StringReader;
19 import java.math.BigInteger;
20 import java.nio.charset.StandardCharsets;
21 import java.util.ArrayList;
22 import java.util.HashSet;
23 import java.util.List;
25 import javax.xml.parsers.DocumentBuilderFactory;
26 import javax.xml.parsers.ParserConfigurationException;
27 import javax.xml.transform.dom.DOMSource;
28 import org.eclipse.jdt.annotation.NonNull;
29 import org.opendaylight.yangtools.concepts.Either;
30 import org.opendaylight.yangtools.util.ImmutableOffsetMapTemplate;
31 import org.opendaylight.yangtools.yang.common.Decimal64;
32 import org.opendaylight.yangtools.yang.common.Empty;
33 import org.opendaylight.yangtools.yang.common.QName;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
35 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
38 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
39 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42 import org.w3c.dom.Element;
43 import org.xml.sax.InputSource;
44 import org.xml.sax.SAXException;
47 * NormalizedNodeInputStreamReader reads the byte stream and constructs the normalized node including its children
48 * nodes. This process goes in recursive manner, where each NodeTypes object signifies the start of the object, except
49 * END_NODE. If a node can have children, then that node's end is calculated based on appearance of END_NODE.
51 abstract class AbstractLithiumDataInput extends AbstractLegacyDataInput {
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 -> streamAnyxml(writer);
70 case LithiumNode.AUGMENTATION_NODE -> streamAugmentation(writer);
71 case LithiumNode.CHOICE_NODE -> streamChoice(writer);
72 case LithiumNode.CONTAINER_NODE -> streamContainer(writer);
73 case LithiumNode.LEAF_NODE -> streamLeaf(writer);
74 case LithiumNode.LEAF_SET -> streamLeafSet(writer);
75 case LithiumNode.ORDERED_LEAF_SET -> streamOrderedLeafSet(writer);
76 case LithiumNode.LEAF_SET_ENTRY_NODE -> streamLeafSetEntry(writer);
77 case LithiumNode.MAP_ENTRY_NODE -> streamMapEntry(writer);
78 case LithiumNode.MAP_NODE -> streamMap(writer);
79 case LithiumNode.ORDERED_MAP_NODE -> streamOrderedMap(writer);
80 case LithiumNode.UNKEYED_LIST -> streamUnkeyedList(writer);
81 case LithiumNode.UNKEYED_LIST_ITEM -> streamUnkeyedListItem(writer);
82 default -> throw new InvalidNormalizedNodeStreamException("Unexpected node " + nodeType);
86 private void streamAnyxml(final NormalizedNodeStreamWriter writer) throws IOException {
87 final NodeIdentifier identifier = readNodeIdentifier();
88 LOG.trace("Streaming anyxml node {}", identifier);
90 final DOMSource value = readDOMSource();
91 if (writer.startAnyxmlNode(identifier, DOMSource.class)) {
92 writer.domSourceValue(value);
97 private void streamAugmentation(final NormalizedNodeStreamWriter writer) throws IOException {
98 final var augIdentifier = readAugmentationIdentifier();
99 LOG.trace("Streaming augmentation node {}", augIdentifier);
100 for (byte nodeType = input.readByte(); nodeType != LithiumNode.END_NODE; nodeType = input.readByte()) {
101 streamNormalizedNode(writer, nodeType);
105 private void streamChoice(final NormalizedNodeStreamWriter writer) throws IOException {
106 final NodeIdentifier identifier = readNodeIdentifier();
107 LOG.trace("Streaming choice node {}", identifier);
108 writer.startChoiceNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
109 commonStreamContainer(writer);
112 private void streamContainer(final NormalizedNodeStreamWriter writer) throws IOException {
113 final NodeIdentifier identifier = readNodeIdentifier();
114 LOG.trace("Streaming container node {}", identifier);
115 writer.startContainerNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
116 commonStreamContainer(writer);
119 private void streamLeaf(final NormalizedNodeStreamWriter writer) throws IOException {
121 endLeaf(writer, readObject());
124 // Leaf inside a MapEntryNode, it can potentially be a key leaf, in which case we want to de-duplicate values.
125 private void streamLeaf(final NormalizedNodeStreamWriter writer, final NodeIdentifierWithPredicates entryId)
127 final NodeIdentifier identifier = startLeaf(writer);
128 final Object value = readObject();
129 final Object entryValue = entryId.getValue(identifier.getNodeType());
130 endLeaf(writer, entryValue == null ? value : entryValue);
133 private NodeIdentifier startLeaf(final NormalizedNodeStreamWriter writer) throws IOException {
134 final NodeIdentifier identifier = readNodeIdentifier();
135 LOG.trace("Streaming leaf node {}", identifier);
136 writer.startLeafNode(identifier);
140 private static void endLeaf(final NormalizedNodeStreamWriter writer, final Object value) throws IOException {
141 writer.scalarValue(value);
145 private void streamLeafSet(final NormalizedNodeStreamWriter writer) throws IOException {
146 final NodeIdentifier identifier = readNodeIdentifier();
147 LOG.trace("Streaming leaf set node {}", identifier);
148 writer.startLeafSet(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
149 commonStreamLeafSet(writer, identifier);
152 private void streamOrderedLeafSet(final NormalizedNodeStreamWriter writer) throws IOException {
153 final NodeIdentifier identifier = readNodeIdentifier();
154 LOG.trace("Streaming ordered leaf set node {}", identifier);
155 writer.startOrderedLeafSet(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
156 commonStreamLeafSet(writer, identifier);
159 private void commonStreamLeafSet(final NormalizedNodeStreamWriter writer, final NodeIdentifier identifier)
161 lastLeafSetQName = identifier.getNodeType();
163 commonStreamContainer(writer);
165 // Make sure we never leak this
166 lastLeafSetQName = null;
170 private void streamLeafSetEntry(final NormalizedNodeStreamWriter writer) throws IOException {
171 final QName name = lastLeafSetQName != null ? lastLeafSetQName : readQName();
172 final Object value = readObject();
173 final NodeWithValue<Object> leafIdentifier = new NodeWithValue<>(name, value);
174 LOG.trace("Streaming leaf set entry node {}, value {}", leafIdentifier, value);
175 writer.startLeafSetEntryNode(leafIdentifier);
176 writer.scalarValue(value);
180 private void streamMap(final NormalizedNodeStreamWriter writer) throws IOException {
181 final NodeIdentifier identifier = readNodeIdentifier();
182 LOG.trace("Streaming map node {}", identifier);
183 writer.startMapNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
184 commonStreamContainer(writer);
187 private void streamOrderedMap(final NormalizedNodeStreamWriter writer) throws IOException {
188 final NodeIdentifier identifier = readNodeIdentifier();
189 LOG.trace("Streaming ordered map node {}", identifier);
190 writer.startOrderedMapNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
191 commonStreamContainer(writer);
194 private void streamMapEntry(final NormalizedNodeStreamWriter writer) throws IOException {
195 final NodeIdentifierWithPredicates entryIdentifier = readNormalizedNodeWithPredicates();
196 LOG.trace("Streaming map entry node {}", entryIdentifier);
197 writer.startMapEntryNode(entryIdentifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
199 // Same loop as commonStreamContainer(), but ...
200 for (byte nodeType = input.readByte(); nodeType != LithiumNode.END_NODE; nodeType = input.readByte()) {
201 if (nodeType == LithiumNode.LEAF_NODE) {
202 // ... leaf nodes may need de-duplication
203 streamLeaf(writer, entryIdentifier);
205 streamNormalizedNode(writer, nodeType);
211 private void streamUnkeyedList(final NormalizedNodeStreamWriter writer) throws IOException {
212 final NodeIdentifier identifier = readNodeIdentifier();
213 LOG.trace("Streaming unkeyed list node {}", identifier);
214 writer.startUnkeyedList(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
215 commonStreamContainer(writer);
218 private void streamUnkeyedListItem(final NormalizedNodeStreamWriter writer) throws IOException {
219 final NodeIdentifier identifier = readNodeIdentifier();
220 LOG.trace("Streaming unkeyed list item node {}", identifier);
221 writer.startUnkeyedListItem(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
222 commonStreamContainer(writer);
225 private void commonStreamContainer(final NormalizedNodeStreamWriter writer) throws IOException {
226 for (byte nodeType = input.readByte(); nodeType != LithiumNode.END_NODE; nodeType = input.readByte()) {
227 streamNormalizedNode(writer, nodeType);
232 private DOMSource readDOMSource() throws IOException {
233 String xml = readObject().toString();
235 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
236 factory.setNamespaceAware(true);
237 Element node = factory.newDocumentBuilder().parse(
238 new InputSource(new StringReader(xml))).getDocumentElement();
239 return new DOMSource(node);
240 } catch (SAXException | ParserConfigurationException e) {
241 throw new IOException("Error parsing XML: " + xml, e);
245 final QName defaultReadQName() throws IOException {
246 // Read in the same sequence of writing
247 String localName = readCodedString();
248 String namespace = readCodedString();
249 String revision = Strings.emptyToNull(readCodedString());
251 return QNameFactory.create(localName, namespace, revision);
254 final String readCodedString() throws IOException {
255 final byte valueType = input.readByte();
256 return switch (valueType) {
257 case LithiumTokens.IS_NULL_VALUE -> null;
258 case LithiumTokens.IS_CODE_VALUE -> {
259 final int code = input.readInt();
261 yield codedStringMap.get(code);
262 } catch (IndexOutOfBoundsException e) {
263 throw new IOException("String code " + code + " was not found", e);
266 case LithiumTokens.IS_STRING_VALUE -> {
267 final String value = input.readUTF().intern();
268 codedStringMap.add(value);
271 default -> throw new IOException("Unhandled string value type " + valueType);
275 private ImmutableSet<QName> readQNameSet() throws IOException {
276 // Read the children count
277 final int count = input.readInt();
278 final var children = ImmutableSet.<QName>builderWithExpectedSize(count);
279 for (int i = 0; i < count; i++) {
280 children.add(readQName());
282 return children.build();
285 abstract @NonNull LegacyAugmentationIdentifier readAugmentationIdentifier() throws IOException;
287 abstract @NonNull NodeIdentifier readNodeIdentifier() throws IOException;
289 final @NonNull LegacyAugmentationIdentifier defaultReadAugmentationIdentifier() throws IOException {
290 return new LegacyAugmentationIdentifier(readQNameSet());
293 private @NonNull NodeIdentifierWithPredicates readNormalizedNodeWithPredicates() throws IOException {
294 final QName qname = readQName();
295 final int count = input.readInt();
298 return NodeIdentifierWithPredicates.of(qname);
300 return NodeIdentifierWithPredicates.of(qname, readQName(), readObject());
302 // ImmutableList is used by ImmutableOffsetMapTemplate for lookups, hence we use that.
303 final Builder<QName> keys = ImmutableList.builderWithExpectedSize(count);
304 final Object[] values = new Object[count];
305 for (int i = 0; i < count; i++) {
306 keys.add(readQName());
307 values[i] = readObject();
310 return NodeIdentifierWithPredicates.of(qname, ImmutableOffsetMapTemplate.ordered(keys.build())
311 .instantiateWithValues(values));
315 private Object readObject() throws IOException {
316 byte objectType = input.readByte();
317 return switch (objectType) {
318 case LithiumValue.BITS_TYPE -> readObjSet();
319 case LithiumValue.BOOL_TYPE -> input.readBoolean();
320 case LithiumValue.BYTE_TYPE -> input.readByte();
321 case LithiumValue.INT_TYPE -> input.readInt();
322 case LithiumValue.LONG_TYPE -> input.readLong();
323 case LithiumValue.QNAME_TYPE -> readQName();
324 case LithiumValue.SHORT_TYPE -> input.readShort();
325 case LithiumValue.STRING_TYPE -> input.readUTF();
326 case LithiumValue.STRING_BYTES_TYPE -> readStringBytes();
327 case LithiumValue.BIG_DECIMAL_TYPE -> Decimal64.valueOf(input.readUTF());
328 case LithiumValue.BIG_INTEGER_TYPE -> new BigInteger(input.readUTF());
329 case LithiumValue.BINARY_TYPE -> {
330 byte[] bytes = new byte[input.readInt()];
331 input.readFully(bytes);
334 case LithiumValue.YANG_IDENTIFIER_TYPE -> readYangInstanceIdentifierInternal();
335 case LithiumValue.EMPTY_TYPE, LithiumValue.NULL_TYPE ->
336 // Leaf nodes no longer allow null values and thus we no longer emit null values. Previously, the "empty"
337 // yang type was represented as null so we translate an incoming null value to Empty. It was possible for
338 // a BI user to set a string leaf to null and we're rolling the dice here but the chances for that are
339 // very low. We'd have to know the yang type but, even if we did, we can't let a null value pass upstream
340 // so we'd have to drop the leaf which might cause other issues.
346 private String readStringBytes() throws IOException {
347 byte[] bytes = new byte[input.readInt()];
348 input.readFully(bytes);
349 return new String(bytes, StandardCharsets.UTF_8);
353 public final YangInstanceIdentifier readYangInstanceIdentifier() throws IOException {
354 return readYangInstanceIdentifierInternal();
357 private @NonNull YangInstanceIdentifier readYangInstanceIdentifierInternal() throws IOException {
358 int size = input.readInt();
359 final Builder<PathArgument> pathArguments = ImmutableList.builderWithExpectedSize(size);
360 for (int i = 0; i < size; i++) {
361 pathArguments.add(readPathArgument());
363 return YangInstanceIdentifier.of(pathArguments.build());
366 private Set<String> readObjSet() throws IOException {
367 int count = input.readInt();
368 Set<String> children = new HashSet<>(count);
369 for (int i = 0; i < count; i++) {
370 children.add(readCodedString());
376 @Deprecated(since = "11.0.0", forRemoval = true)
377 public final Either<PathArgument, LegacyPathArgument> readLegacyPathArgument() throws IOException {
379 int type = input.readByte();
380 return switch (type) {
381 case LithiumPathArgument.AUGMENTATION_IDENTIFIER -> Either.ofSecond(readAugmentationIdentifier());
382 case LithiumPathArgument.NODE_IDENTIFIER -> Either.ofFirst(readNodeIdentifier());
383 case LithiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES ->
384 Either.ofFirst(readNormalizedNodeWithPredicates());
385 case LithiumPathArgument.NODE_IDENTIFIER_WITH_VALUE ->
386 Either.ofFirst(new NodeWithValue<>(readQName(), readObject()));
387 default -> throw new InvalidNormalizedNodeStreamException("Unexpected PathArgument type " + type);