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 AbstractNormalizedNodeDataInput {
53 private static final Logger LOG = LoggerFactory.getLogger(AbstractLithiumDataInput.class);
55 private final List<String> codedStringMap = new ArrayList<>();
57 private QName lastLeafSetQName;
59 AbstractLithiumDataInput(final DataInput input) {
64 public final void streamNormalizedNode(final NormalizedNodeStreamWriter writer) throws IOException {
65 streamNormalizedNode(requireNonNull(writer), input.readByte());
68 private void streamNormalizedNode(final NormalizedNodeStreamWriter writer, final byte nodeType) throws IOException {
70 case LithiumNode.ANY_XML_NODE -> streamAnyxml(writer);
71 case LithiumNode.AUGMENTATION_NODE -> streamAugmentation(writer);
72 case LithiumNode.CHOICE_NODE -> streamChoice(writer);
73 case LithiumNode.CONTAINER_NODE -> streamContainer(writer);
74 case LithiumNode.LEAF_NODE -> streamLeaf(writer);
75 case LithiumNode.LEAF_SET -> streamLeafSet(writer);
76 case LithiumNode.ORDERED_LEAF_SET -> streamOrderedLeafSet(writer);
77 case LithiumNode.LEAF_SET_ENTRY_NODE -> streamLeafSetEntry(writer);
78 case LithiumNode.MAP_ENTRY_NODE -> streamMapEntry(writer);
79 case LithiumNode.MAP_NODE -> streamMap(writer);
80 case LithiumNode.ORDERED_MAP_NODE -> streamOrderedMap(writer);
81 case LithiumNode.UNKEYED_LIST -> streamUnkeyedList(writer);
82 case LithiumNode.UNKEYED_LIST_ITEM -> streamUnkeyedListItem(writer);
83 default -> throw new InvalidNormalizedNodeStreamException("Unexpected node " + nodeType);
87 private void streamAnyxml(final NormalizedNodeStreamWriter writer) throws IOException {
88 final NodeIdentifier identifier = readNodeIdentifier();
89 LOG.trace("Streaming anyxml node {}", identifier);
91 final DOMSource value = readDOMSource();
92 if (writer.startAnyxmlNode(identifier, DOMSource.class)) {
93 writer.domSourceValue(value);
98 private void streamAugmentation(final NormalizedNodeStreamWriter writer) throws IOException {
99 final var augIdentifier = readAugmentationIdentifier();
100 LOG.trace("Streaming augmentation node {}", augIdentifier);
101 for (byte nodeType = input.readByte(); nodeType != LithiumNode.END_NODE; nodeType = input.readByte()) {
102 streamNormalizedNode(writer, nodeType);
106 private void streamChoice(final NormalizedNodeStreamWriter writer) throws IOException {
107 final NodeIdentifier identifier = readNodeIdentifier();
108 LOG.trace("Streaming choice node {}", identifier);
109 writer.startChoiceNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
110 commonStreamContainer(writer);
113 private void streamContainer(final NormalizedNodeStreamWriter writer) throws IOException {
114 final NodeIdentifier identifier = readNodeIdentifier();
115 LOG.trace("Streaming container node {}", identifier);
116 writer.startContainerNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
117 commonStreamContainer(writer);
120 private void streamLeaf(final NormalizedNodeStreamWriter writer) throws IOException {
122 endLeaf(writer, readObject());
125 // Leaf inside a MapEntryNode, it can potentially be a key leaf, in which case we want to de-duplicate values.
126 private void streamLeaf(final NormalizedNodeStreamWriter writer, final NodeIdentifierWithPredicates entryId)
128 final NodeIdentifier identifier = startLeaf(writer);
129 final Object value = readObject();
130 final Object entryValue = entryId.getValue(identifier.getNodeType());
131 endLeaf(writer, entryValue == null ? value : entryValue);
134 private NodeIdentifier startLeaf(final NormalizedNodeStreamWriter writer) throws IOException {
135 final NodeIdentifier identifier = readNodeIdentifier();
136 LOG.trace("Streaming leaf node {}", identifier);
137 writer.startLeafNode(identifier);
141 private static void endLeaf(final NormalizedNodeStreamWriter writer, final Object value) throws IOException {
142 writer.scalarValue(value);
146 private void streamLeafSet(final NormalizedNodeStreamWriter writer) throws IOException {
147 final NodeIdentifier identifier = readNodeIdentifier();
148 LOG.trace("Streaming leaf set node {}", identifier);
149 writer.startLeafSet(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
150 commonStreamLeafSet(writer, identifier);
153 private void streamOrderedLeafSet(final NormalizedNodeStreamWriter writer) throws IOException {
154 final NodeIdentifier identifier = readNodeIdentifier();
155 LOG.trace("Streaming ordered leaf set node {}", identifier);
156 writer.startOrderedLeafSet(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
157 commonStreamLeafSet(writer, identifier);
160 private void commonStreamLeafSet(final NormalizedNodeStreamWriter writer, final NodeIdentifier identifier)
162 lastLeafSetQName = identifier.getNodeType();
164 commonStreamContainer(writer);
166 // Make sure we never leak this
167 lastLeafSetQName = null;
171 private void streamLeafSetEntry(final NormalizedNodeStreamWriter writer) throws IOException {
172 final QName name = lastLeafSetQName != null ? lastLeafSetQName : readQName();
173 final Object value = readObject();
174 final NodeWithValue<Object> leafIdentifier = new NodeWithValue<>(name, value);
175 LOG.trace("Streaming leaf set entry node {}, value {}", leafIdentifier, value);
176 writer.startLeafSetEntryNode(leafIdentifier);
177 writer.scalarValue(value);
181 private void streamMap(final NormalizedNodeStreamWriter writer) throws IOException {
182 final NodeIdentifier identifier = readNodeIdentifier();
183 LOG.trace("Streaming map node {}", identifier);
184 writer.startMapNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
185 commonStreamContainer(writer);
188 private void streamOrderedMap(final NormalizedNodeStreamWriter writer) throws IOException {
189 final NodeIdentifier identifier = readNodeIdentifier();
190 LOG.trace("Streaming ordered map node {}", identifier);
191 writer.startOrderedMapNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
192 commonStreamContainer(writer);
195 private void streamMapEntry(final NormalizedNodeStreamWriter writer) throws IOException {
196 final NodeIdentifierWithPredicates entryIdentifier = readNormalizedNodeWithPredicates();
197 LOG.trace("Streaming map entry node {}", entryIdentifier);
198 writer.startMapEntryNode(entryIdentifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
200 // Same loop as commonStreamContainer(), but ...
201 for (byte nodeType = input.readByte(); nodeType != LithiumNode.END_NODE; nodeType = input.readByte()) {
202 if (nodeType == LithiumNode.LEAF_NODE) {
203 // ... leaf nodes may need de-duplication
204 streamLeaf(writer, entryIdentifier);
206 streamNormalizedNode(writer, nodeType);
212 private void streamUnkeyedList(final NormalizedNodeStreamWriter writer) throws IOException {
213 final NodeIdentifier identifier = readNodeIdentifier();
214 LOG.trace("Streaming unkeyed list node {}", identifier);
215 writer.startUnkeyedList(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
216 commonStreamContainer(writer);
219 private void streamUnkeyedListItem(final NormalizedNodeStreamWriter writer) throws IOException {
220 final NodeIdentifier identifier = readNodeIdentifier();
221 LOG.trace("Streaming unkeyed list item node {}", identifier);
222 writer.startUnkeyedListItem(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
223 commonStreamContainer(writer);
226 private void commonStreamContainer(final NormalizedNodeStreamWriter writer) throws IOException {
227 for (byte nodeType = input.readByte(); nodeType != LithiumNode.END_NODE; nodeType = input.readByte()) {
228 streamNormalizedNode(writer, nodeType);
233 private DOMSource readDOMSource() throws IOException {
234 String xml = readObject().toString();
236 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
237 factory.setNamespaceAware(true);
238 Element node = factory.newDocumentBuilder().parse(
239 new InputSource(new StringReader(xml))).getDocumentElement();
240 return new DOMSource(node);
241 } catch (SAXException | ParserConfigurationException e) {
242 throw new IOException("Error parsing XML: " + xml, e);
246 final QName defaultReadQName() throws IOException {
247 // Read in the same sequence of writing
248 String localName = readCodedString();
249 String namespace = readCodedString();
250 String revision = Strings.emptyToNull(readCodedString());
252 return QNameFactory.create(localName, namespace, revision);
255 final String readCodedString() throws IOException {
256 final byte valueType = input.readByte();
257 return switch (valueType) {
258 case LithiumTokens.IS_NULL_VALUE -> null;
259 case LithiumTokens.IS_CODE_VALUE -> {
260 final int code = input.readInt();
262 yield codedStringMap.get(code);
263 } catch (IndexOutOfBoundsException e) {
264 throw new IOException("String code " + code + " was not found", e);
267 case LithiumTokens.IS_STRING_VALUE -> {
268 final String value = input.readUTF().intern();
269 codedStringMap.add(value);
272 default -> throw new IOException("Unhandled string value type " + valueType);
276 private ImmutableSet<QName> readQNameSet() throws IOException {
277 // Read the children count
278 final int count = input.readInt();
279 final var children = ImmutableSet.<QName>builderWithExpectedSize(count);
280 for (int i = 0; i < count; i++) {
281 children.add(readQName());
283 return children.build();
286 abstract @NonNull LegacyAugmentationIdentifier readAugmentationIdentifier() throws IOException;
288 abstract @NonNull NodeIdentifier readNodeIdentifier() throws IOException;
290 final @NonNull LegacyAugmentationIdentifier defaultReadAugmentationIdentifier() throws IOException {
291 return new LegacyAugmentationIdentifier(readQNameSet());
294 private @NonNull NodeIdentifierWithPredicates readNormalizedNodeWithPredicates() throws IOException {
295 final QName qname = readQName();
296 final int count = input.readInt();
299 return NodeIdentifierWithPredicates.of(qname);
301 return NodeIdentifierWithPredicates.of(qname, readQName(), readObject());
303 // ImmutableList is used by ImmutableOffsetMapTemplate for lookups, hence we use that.
304 final Builder<QName> keys = ImmutableList.builderWithExpectedSize(count);
305 final Object[] values = new Object[count];
306 for (int i = 0; i < count; i++) {
307 keys.add(readQName());
308 values[i] = readObject();
311 return NodeIdentifierWithPredicates.of(qname, ImmutableOffsetMapTemplate.ordered(keys.build())
312 .instantiateWithValues(values));
316 private Object readObject() throws IOException {
317 byte objectType = input.readByte();
318 return switch (objectType) {
319 case LithiumValue.BITS_TYPE -> readObjSet();
320 case LithiumValue.BOOL_TYPE -> input.readBoolean();
321 case LithiumValue.BYTE_TYPE -> input.readByte();
322 case LithiumValue.INT_TYPE -> input.readInt();
323 case LithiumValue.LONG_TYPE -> input.readLong();
324 case LithiumValue.QNAME_TYPE -> readQName();
325 case LithiumValue.SHORT_TYPE -> input.readShort();
326 case LithiumValue.STRING_TYPE -> input.readUTF();
327 case LithiumValue.STRING_BYTES_TYPE -> readStringBytes();
328 case LithiumValue.BIG_DECIMAL_TYPE -> Decimal64.valueOf(input.readUTF());
329 case LithiumValue.BIG_INTEGER_TYPE -> new BigInteger(input.readUTF());
330 case LithiumValue.BINARY_TYPE -> {
331 byte[] bytes = new byte[input.readInt()];
332 input.readFully(bytes);
335 case LithiumValue.YANG_IDENTIFIER_TYPE -> readYangInstanceIdentifierInternal();
336 case LithiumValue.EMPTY_TYPE, LithiumValue.NULL_TYPE ->
337 // Leaf nodes no longer allow null values and thus we no longer emit null values. Previously, the "empty"
338 // yang type was represented as null so we translate an incoming null value to Empty. It was possible for
339 // a BI user to set a string leaf to null and we're rolling the dice here but the chances for that are
340 // very low. We'd have to know the yang type but, even if we did, we can't let a null value pass upstream
341 // so we'd have to drop the leaf which might cause other issues.
347 private String readStringBytes() throws IOException {
348 byte[] bytes = new byte[input.readInt()];
349 input.readFully(bytes);
350 return new String(bytes, StandardCharsets.UTF_8);
354 public final YangInstanceIdentifier readYangInstanceIdentifier() throws IOException {
355 return readYangInstanceIdentifierInternal();
358 private @NonNull YangInstanceIdentifier readYangInstanceIdentifierInternal() throws IOException {
359 int size = input.readInt();
360 final Builder<PathArgument> pathArguments = ImmutableList.builderWithExpectedSize(size);
361 for (int i = 0; i < size; i++) {
362 pathArguments.add(readPathArgument());
364 return YangInstanceIdentifier.create(pathArguments.build());
367 private Set<String> readObjSet() throws IOException {
368 int count = input.readInt();
369 Set<String> children = new HashSet<>(count);
370 for (int i = 0; i < count; i++) {
371 children.add(readCodedString());
377 @Deprecated(since = "11.0.0", forRemoval = true)
378 public final Either<PathArgument, LegacyPathArgument> readLegacyPathArgument() throws IOException {
380 int type = input.readByte();
381 return switch (type) {
382 case LithiumPathArgument.AUGMENTATION_IDENTIFIER -> Either.ofSecond(readAugmentationIdentifier());
383 case LithiumPathArgument.NODE_IDENTIFIER -> Either.ofFirst(readNodeIdentifier());
384 case LithiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES ->
385 Either.ofFirst(readNormalizedNodeWithPredicates());
386 case LithiumPathArgument.NODE_IDENTIFIER_WITH_VALUE ->
387 Either.ofFirst(new NodeWithValue<>(readQName(), readObject()));
388 default -> throw new InvalidNormalizedNodeStreamException("Unexpected PathArgument type " + type);