2 * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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 com.google.common.base.Preconditions.checkArgument;
12 import java.io.DataOutput;
13 import java.io.IOException;
14 import java.io.StringWriter;
15 import java.math.BigDecimal;
16 import java.nio.charset.StandardCharsets;
17 import java.util.ArrayDeque;
18 import java.util.Deque;
19 import java.util.HashMap;
20 import java.util.List;
22 import java.util.Map.Entry;
23 import java.util.Optional;
25 import javax.xml.transform.TransformerException;
26 import javax.xml.transform.TransformerFactory;
27 import javax.xml.transform.dom.DOMSource;
28 import javax.xml.transform.stream.StreamResult;
29 import org.eclipse.jdt.annotation.NonNull;
30 import org.opendaylight.yangtools.yang.common.Decimal64;
31 import org.opendaylight.yangtools.yang.common.Empty;
32 import org.opendaylight.yangtools.yang.common.QName;
33 import org.opendaylight.yangtools.yang.common.QNameModule;
34 import org.opendaylight.yangtools.yang.common.Revision;
35 import org.opendaylight.yangtools.yang.common.Uint16;
36 import org.opendaylight.yangtools.yang.common.Uint32;
37 import org.opendaylight.yangtools.yang.common.Uint64;
38 import org.opendaylight.yangtools.yang.common.Uint8;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
41 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
42 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
43 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
49 * Abstract base class for NormalizedNodeDataOutput based on {@link MagnesiumNode}, {@link MagnesiumPathArgument} and
50 * {@link MagnesiumValue}.
52 final class MagnesiumDataOutput extends AbstractNormalizedNodeDataOutput {
53 private static final Logger LOG = LoggerFactory.getLogger(MagnesiumDataOutput.class);
55 // Marker for encoding state when we have entered startLeafNode() within a startMapEntry() and that leaf corresponds
56 // to a key carried within NodeIdentifierWithPredicates.
57 private static final Object KEY_LEAF_STATE = new Object();
58 // Marker for nodes which have simple content and do not use END_NODE marker to terminate
59 private static final Object NO_ENDNODE_STATE = new Object();
61 private static final TransformerFactory TF = TransformerFactory.newInstance();
64 * Stack tracking encoding state. In general we track the node identifier of the currently-open element, but there
65 * are a few other circumstances where we push other objects. See {@link #KEY_LEAF_STATE} and
66 * {@link #NO_ENDNODE_STATE}.
68 private final Deque<Object> stack = new ArrayDeque<>();
71 private final Map<AugmentationIdentifier, Integer> aidCodeMap = new HashMap<>();
72 private final Map<QNameModule, Integer> moduleCodeMap = new HashMap<>();
73 private final Map<String, Integer> stringCodeMap = new HashMap<>();
74 private final Map<QName, Integer> qnameCodeMap = new HashMap<>();
76 MagnesiumDataOutput(final DataOutput output) {
81 public void startLeafNode(final NodeIdentifier name) throws IOException {
82 final Object current = stack.peek();
83 if (current instanceof NodeIdentifierWithPredicates nip) {
84 final QName qname = name.getNodeType();
85 if (nip.containsKey(qname)) {
86 writeQNameNode(MagnesiumNode.NODE_LEAF | MagnesiumNode.PREDICATE_ONE, qname);
87 stack.push(KEY_LEAF_STATE);
92 startSimpleNode(MagnesiumNode.NODE_LEAF, name);
96 public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
97 startQNameNode(MagnesiumNode.NODE_LEAFSET, name);
101 public void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
102 startQNameNode(MagnesiumNode.NODE_LEAFSET_ORDERED, name);
106 public void startLeafSetEntryNode(final NodeWithValue<?> name) throws IOException {
107 if (matchesParentQName(name.getNodeType())) {
108 output.writeByte(MagnesiumNode.NODE_LEAFSET_ENTRY);
109 stack.push(NO_ENDNODE_STATE);
111 startSimpleNode(MagnesiumNode.NODE_LEAFSET_ENTRY, name);
116 public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
117 startQNameNode(MagnesiumNode.NODE_CONTAINER, name);
121 public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
122 startQNameNode(MagnesiumNode.NODE_LIST, name);
126 public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
127 startInheritedNode(MagnesiumNode.NODE_LIST_ENTRY, name);
131 public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
132 startQNameNode(MagnesiumNode.NODE_MAP, name);
136 public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
138 final int size = identifier.size();
140 startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_ONE), identifier);
141 } else if (size == 0) {
142 startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_ZERO), identifier);
143 } else if (size < 256) {
144 startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_1B), identifier);
145 output.writeByte(size);
147 startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_4B), identifier);
148 output.writeInt(size);
151 writePredicates(identifier);
155 public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
156 startQNameNode(MagnesiumNode.NODE_MAP_ORDERED, name);
160 public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
161 startQNameNode(MagnesiumNode.NODE_CHOICE, name);
165 public void startAugmentationNode(final AugmentationIdentifier identifier) throws IOException {
166 final Integer code = aidCodeMap.get(identifier);
168 aidCodeMap.put(identifier, aidCodeMap.size());
169 output.writeByte(MagnesiumNode.NODE_AUGMENTATION | MagnesiumNode.ADDR_DEFINE);
170 final Set<QName> qnames = identifier.getPossibleChildNames();
171 output.writeInt(qnames.size());
172 for (QName qname : qnames) {
173 writeQNameInternal(qname);
176 writeNodeType(MagnesiumNode.NODE_AUGMENTATION, code);
178 stack.push(identifier);
182 public boolean startAnyxmlNode(final NodeIdentifier name, final Class<?> objectModel) throws IOException {
183 if (DOMSource.class.isAssignableFrom(objectModel)) {
184 startSimpleNode(MagnesiumNode.NODE_ANYXML, name);
191 public void domSourceValue(final DOMSource value) throws IOException {
192 final StringWriter writer = new StringWriter();
194 TF.newTransformer().transform(value, new StreamResult(writer));
195 } catch (TransformerException e) {
196 throw new IOException("Error writing anyXml", e);
198 writeValue(writer.toString());
202 public void endNode() throws IOException {
203 if (stack.pop() instanceof PathArgument) {
204 output.writeByte(MagnesiumNode.NODE_END);
209 public void scalarValue(final Object value) throws IOException {
210 if (KEY_LEAF_STATE.equals(stack.peek())) {
211 LOG.trace("Inside a map entry key leaf, not emitting value {}", value);
217 @Override short streamVersion() {
218 return TokenTypes.MAGNESIUM_VERSION;
221 @Override void writeQNameInternal(final QName qname) throws IOException {
222 final Integer code = qnameCodeMap.get(qname);
224 output.writeByte(MagnesiumValue.QNAME);
231 @Override void writePathArgumentInternal(final PathArgument pathArgument) throws IOException {
232 if (pathArgument instanceof NodeIdentifier nid) {
233 writeNodeIdentifier(nid);
234 } else if (pathArgument instanceof NodeIdentifierWithPredicates nip) {
235 writeNodeIdentifierWithPredicates(nip);
236 } else if (pathArgument instanceof AugmentationIdentifier augid) {
237 writeAugmentationIdentifier(augid);
238 } else if (pathArgument instanceof NodeWithValue<?> niv) {
239 writeNodeWithValue(niv);
241 throw new IOException("Unhandled PathArgument " + pathArgument);
245 private void writeAugmentationIdentifier(final AugmentationIdentifier identifier) throws IOException {
246 final Set<QName> qnames = identifier.getPossibleChildNames();
247 final int size = qnames.size();
249 output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER
250 | size << MagnesiumPathArgument.AID_COUNT_SHIFT);
251 } else if (size < 256) {
252 output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER | MagnesiumPathArgument.AID_COUNT_1B);
253 output.writeByte(size);
254 } else if (size < 65536) {
255 output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER | MagnesiumPathArgument.AID_COUNT_2B);
256 output.writeShort(size);
258 output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER | MagnesiumPathArgument.AID_COUNT_4B);
259 output.writeInt(size);
262 for (QName qname : qnames) {
263 writeQNameInternal(qname);
267 private void writeNodeIdentifier(final NodeIdentifier identifier) throws IOException {
268 writePathArgumentQName(identifier.getNodeType(), MagnesiumPathArgument.NODE_IDENTIFIER);
271 private void writeNodeIdentifierWithPredicates(final NodeIdentifierWithPredicates identifier) throws IOException {
272 final int size = identifier.size();
274 writePathArgumentQName(identifier.getNodeType(),
275 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES
276 | size << MagnesiumPathArgument.SIZE_SHIFT));
277 } else if (size < 256) {
278 writePathArgumentQName(identifier.getNodeType(),
279 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_1B));
280 output.writeByte(size);
281 } else if (size < 65536) {
282 writePathArgumentQName(identifier.getNodeType(),
283 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_2B));
284 output.writeShort(size);
286 writePathArgumentQName(identifier.getNodeType(),
287 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_4B));
288 output.writeInt(size);
291 writePredicates(identifier);
294 private void writePredicates(final NodeIdentifierWithPredicates identifier) throws IOException {
295 for (Entry<QName, Object> e : identifier.entrySet()) {
296 writeQNameInternal(e.getKey());
297 writeObject(e.getValue());
301 private void writeNodeWithValue(final NodeWithValue<?> identifier) throws IOException {
302 writePathArgumentQName(identifier.getNodeType(), MagnesiumPathArgument.NODE_WITH_VALUE);
303 writeObject(identifier.getValue());
306 private void writePathArgumentQName(final QName qname, final byte typeHeader) throws IOException {
307 final Integer code = qnameCodeMap.get(qname);
309 final int val = code;
311 output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_1B);
312 output.writeByte(val);
313 } else if (val < 65792) {
314 output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_2B);
315 output.writeShort(val - 256);
317 output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_4B);
318 output.writeInt(val);
321 // implied '| MagnesiumPathArgument.QNAME_DEF'
322 output.writeByte(typeHeader);
327 @Override void writeYangInstanceIdentifierInternal(final YangInstanceIdentifier identifier) throws IOException {
328 writeValue(identifier);
331 private void writeObject(final @NonNull Object value) throws IOException {
332 if (value instanceof String str) {
334 } else if (value instanceof Boolean bool) {
336 } else if (value instanceof Byte byteVal) {
338 } else if (value instanceof Short shortVal) {
339 writeValue(shortVal);
340 } else if (value instanceof Integer intVal) {
342 } else if (value instanceof Long longVal) {
344 } else if (value instanceof Uint8 uint8) {
346 } else if (value instanceof Uint16 uint16) {
348 } else if (value instanceof Uint32 uint32) {
350 } else if (value instanceof Uint64 uint64) {
352 } else if (value instanceof QName qname) {
353 writeQNameInternal(qname);
354 } else if (value instanceof YangInstanceIdentifier id) {
356 } else if (value instanceof byte[] bytes) {
358 } else if (value instanceof Empty) {
359 output.writeByte(MagnesiumValue.EMPTY);
360 } else if (value instanceof Set<?> set) {
362 } else if (value instanceof BigDecimal || value instanceof Decimal64) {
363 output.writeByte(MagnesiumValue.BIGDECIMAL);
364 output.writeUTF(value.toString());
366 throw new IOException("Unhandled value type " + value.getClass());
370 private void writeValue(final boolean value) throws IOException {
371 output.writeByte(value ? MagnesiumValue.BOOLEAN_TRUE : MagnesiumValue.BOOLEAN_FALSE);
374 private void writeValue(final byte value) throws IOException {
376 output.writeByte(MagnesiumValue.INT8);
377 output.writeByte(value);
379 output.writeByte(MagnesiumValue.INT8_0);
383 private void writeValue(final short value) throws IOException {
385 output.writeByte(MagnesiumValue.INT16);
386 output.writeShort(value);
388 output.writeByte(MagnesiumValue.INT16_0);
392 private void writeValue(final int value) throws IOException {
393 if ((value & 0xFFFF0000) != 0) {
394 output.writeByte(MagnesiumValue.INT32);
395 output.writeInt(value);
396 } else if (value != 0) {
397 output.writeByte(MagnesiumValue.INT32_2B);
398 output.writeShort(value);
400 output.writeByte(MagnesiumValue.INT32_0);
404 private void writeValue(final long value) throws IOException {
405 if ((value & 0xFFFFFFFF00000000L) != 0) {
406 output.writeByte(MagnesiumValue.INT64);
407 output.writeLong(value);
408 } else if (value != 0) {
409 output.writeByte(MagnesiumValue.INT64_4B);
410 output.writeInt((int) value);
412 output.writeByte(MagnesiumValue.INT64_0);
416 private void writeValue(final Uint8 value) throws IOException {
417 final byte b = value.byteValue();
419 output.writeByte(MagnesiumValue.UINT8);
422 output.writeByte(MagnesiumValue.UINT8_0);
426 private void writeValue(final Uint16 value) throws IOException {
427 final short s = value.shortValue();
429 output.writeByte(MagnesiumValue.UINT16);
430 output.writeShort(s);
432 output.writeByte(MagnesiumValue.UINT16_0);
436 private void writeValue(final Uint32 value) throws IOException {
437 final int i = value.intValue();
438 if ((i & 0xFFFF0000) != 0) {
439 output.writeByte(MagnesiumValue.UINT32);
442 output.writeByte(MagnesiumValue.UINT32_2B);
443 output.writeShort(i);
445 output.writeByte(MagnesiumValue.UINT32_0);
449 private void writeValue(final Uint64 value) throws IOException {
450 final long l = value.longValue();
451 if ((l & 0xFFFFFFFF00000000L) != 0) {
452 output.writeByte(MagnesiumValue.UINT64);
455 output.writeByte(MagnesiumValue.UINT64_4B);
456 output.writeInt((int) l);
458 output.writeByte(MagnesiumValue.UINT64_0);
462 private void writeValue(final String value) throws IOException {
463 if (value.isEmpty()) {
464 output.writeByte(MagnesiumValue.STRING_EMPTY);
465 } else if (value.length() <= Short.MAX_VALUE / 2) {
466 output.writeByte(MagnesiumValue.STRING_UTF);
467 output.writeUTF(value);
468 } else if (value.length() <= 1048576) {
469 final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
470 if (bytes.length < 65536) {
471 output.writeByte(MagnesiumValue.STRING_2B);
472 output.writeShort(bytes.length);
474 output.writeByte(MagnesiumValue.STRING_4B);
475 output.writeInt(bytes.length);
479 output.writeByte(MagnesiumValue.STRING_CHARS);
480 output.writeInt(value.length());
481 output.writeChars(value);
485 private void writeValue(final byte[] value) throws IOException {
486 if (value.length < 128) {
487 output.writeByte(MagnesiumValue.BINARY_0 + value.length);
488 } else if (value.length < 384) {
489 output.writeByte(MagnesiumValue.BINARY_1B);
490 output.writeByte(value.length - 128);
491 } else if (value.length < 65920) {
492 output.writeByte(MagnesiumValue.BINARY_2B);
493 output.writeShort(value.length - 384);
495 output.writeByte(MagnesiumValue.BINARY_4B);
496 output.writeInt(value.length);
501 private void writeValue(final YangInstanceIdentifier value) throws IOException {
502 final List<PathArgument> args = value.getPathArguments();
503 final int size = args.size();
505 output.writeByte(MagnesiumValue.YIID);
506 output.writeInt(size);
508 output.writeByte(MagnesiumValue.YIID_0 + size);
510 for (PathArgument arg : args) {
511 writePathArgumentInternal(arg);
515 private void writeValue(final Set<?> value) throws IOException {
516 final int size = value.size();
518 output.writeByte(MagnesiumValue.BITS_0 + size);
519 } else if (size < 285) {
520 output.writeByte(MagnesiumValue.BITS_1B);
521 output.writeByte(size - 29);
522 } else if (size < 65821) {
523 output.writeByte(MagnesiumValue.BITS_2B);
524 output.writeShort(size - 285);
526 output.writeByte(MagnesiumValue.BITS_4B);
527 output.writeInt(size);
530 for (Object bit : value) {
531 checkArgument(bit instanceof String, "Expected value type to be String but was %s", bit);
532 encodeString((String) bit);
536 // Check if the proposed QName matches the parent. This is only effective if the parent is identified by
537 // NodeIdentifier -- which is typically true
538 private boolean matchesParentQName(final QName qname) {
539 final Object current = stack.peek();
540 return current instanceof NodeIdentifier nid && qname.equals(nid.getNodeType());
543 // Start an END_NODE-terminated node, which typically has a QName matching the parent. If that is the case we emit
544 // a parent reference instead of an explicit QName reference -- saving at least one byte
545 private void startInheritedNode(final byte type, final PathArgument name) throws IOException {
546 final QName qname = name.getNodeType();
547 if (matchesParentQName(qname)) {
550 writeQNameNode(type, qname);
555 // Start an END_NODE-terminated node, which needs its QName encoded
556 private void startQNameNode(final byte type, final PathArgument name) throws IOException {
557 writeQNameNode(type, name.getNodeType());
561 // Start a simple node, which is not terminated through END_NODE and encode its QName
562 private void startSimpleNode(final byte type, final PathArgument name) throws IOException {
563 writeQNameNode(type, name.getNodeType());
564 stack.push(NO_ENDNODE_STATE);
567 // Encode a QName-based (i.e. NodeIdentifier*) node with a particular QName. This will either result in a QName
568 // definition, or a reference, where this is encoded along with the node type.
569 private void writeQNameNode(final int type, final @NonNull QName qname) throws IOException {
570 final Integer code = qnameCodeMap.get(qname);
572 output.writeByte(type | MagnesiumNode.ADDR_DEFINE);
575 writeNodeType(type, code);
579 // Write a node type + lookup
580 private void writeNodeType(final int type, final int code) throws IOException {
582 output.writeByte(type | MagnesiumNode.ADDR_LOOKUP_1B);
583 output.writeByte(code);
585 output.writeByte(type | MagnesiumNode.ADDR_LOOKUP_4B);
586 output.writeInt(code);
590 // Encode a QName using lookup tables, resuling either in a reference to an existing entry, or emitting two
592 private void encodeQName(final @NonNull QName qname) throws IOException {
593 final Integer prev = qnameCodeMap.put(qname, qnameCodeMap.size());
595 throw new IOException("Internal coding error: attempted to re-encode " + qname + "%s already encoded as "
599 final QNameModule module = qname.getModule();
600 final Integer code = moduleCodeMap.get(module);
602 moduleCodeMap.put(module, moduleCodeMap.size());
603 encodeString(module.getNamespace().toString());
604 final Optional<Revision> rev = module.getRevision();
605 if (rev.isPresent()) {
606 encodeString(rev.orElseThrow().toString());
608 output.writeByte(MagnesiumValue.STRING_EMPTY);
611 writeModuleRef(code);
613 encodeString(qname.getLocalName());
616 // Encode a String using lookup tables, resulting either in a reference to an existing entry, or emitting as
618 private void encodeString(final @NonNull String str) throws IOException {
619 final Integer code = stringCodeMap.get(str);
623 stringCodeMap.put(str, stringCodeMap.size());
628 // Write a QName with a lookup table reference. This is a combination of asserting the value is a QName plus
629 // the effects of writeRef()
630 private void writeQNameRef(final int code) throws IOException {
631 final int val = code;
633 output.writeByte(MagnesiumValue.QNAME_REF_1B);
634 output.writeByte(val);
635 } else if (val < 65792) {
636 output.writeByte(MagnesiumValue.QNAME_REF_2B);
637 output.writeShort(val - 256);
639 output.writeByte(MagnesiumValue.QNAME_REF_4B);
640 output.writeInt(val);
644 // Write a lookup table reference, which table is being referenced is implied by the caller
645 private void writeRef(final int code) throws IOException {
646 final int val = code;
648 output.writeByte(MagnesiumValue.STRING_REF_1B);
649 output.writeByte(val);
650 } else if (val < 65792) {
651 output.writeByte(MagnesiumValue.STRING_REF_2B);
652 output.writeShort(val - 256);
654 output.writeByte(MagnesiumValue.STRING_REF_4B);
655 output.writeInt(val);
659 // Write a lookup module table reference, which table is being referenced is implied by the caller
660 private void writeModuleRef(final int code) throws IOException {
661 final int val = code;
663 output.writeByte(MagnesiumValue.MODREF_1B);
664 output.writeByte(val);
665 } else if (val < 65792) {
666 output.writeByte(MagnesiumValue.MODREF_2B);
667 output.writeShort(val - 256);
669 output.writeByte(MagnesiumValue.MODREF_4B);
670 output.writeInt(val);