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.math.BigInteger;
17 import java.nio.charset.StandardCharsets;
18 import java.util.ArrayDeque;
19 import java.util.Deque;
20 import java.util.HashMap;
21 import java.util.List;
23 import java.util.Map.Entry;
24 import java.util.Optional;
26 import javax.xml.transform.TransformerException;
27 import javax.xml.transform.TransformerFactory;
28 import javax.xml.transform.dom.DOMSource;
29 import javax.xml.transform.stream.StreamResult;
30 import org.eclipse.jdt.annotation.NonNull;
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.common.QNameModule;
35 import org.opendaylight.yangtools.yang.common.Revision;
36 import org.opendaylight.yangtools.yang.common.Uint16;
37 import org.opendaylight.yangtools.yang.common.Uint32;
38 import org.opendaylight.yangtools.yang.common.Uint64;
39 import org.opendaylight.yangtools.yang.common.Uint8;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
41 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
42 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
43 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
45 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * Abstract base class for NormalizedNodeDataOutput based on {@link MagnesiumNode}, {@link MagnesiumPathArgument} and
51 * {@link MagnesiumValue}.
53 abstract class AbstractMagnesiumDataOutput extends AbstractNormalizedNodeDataOutput {
54 private static final Logger LOG = LoggerFactory.getLogger(AbstractMagnesiumDataOutput.class);
56 // Marker for encoding state when we have entered startLeafNode() within a startMapEntry() and that leaf corresponds
57 // to a key carried within NodeIdentifierWithPredicates.
58 private static final Object KEY_LEAF_STATE = new Object();
59 // Marker for nodes which have simple content and do not use END_NODE marker to terminate
60 private static final Object NO_ENDNODE_STATE = new Object();
62 private static final TransformerFactory TF = TransformerFactory.newInstance();
65 * Stack tracking encoding state. In general we track the node identifier of the currently-open element, but there
66 * are a few other circumstances where we push other objects. See {@link #KEY_LEAF_STATE} and
67 * {@link #NO_ENDNODE_STATE}.
69 private final Deque<Object> stack = new ArrayDeque<>();
72 private final Map<AugmentationIdentifier, Integer> aidCodeMap = new HashMap<>();
73 private final Map<QNameModule, Integer> moduleCodeMap = new HashMap<>();
74 private final Map<String, Integer> stringCodeMap = new HashMap<>();
75 private final Map<QName, Integer> qnameCodeMap = new HashMap<>();
77 AbstractMagnesiumDataOutput(final DataOutput output) {
82 public final void startLeafNode(final NodeIdentifier name) throws IOException {
83 final Object current = stack.peek();
84 if (current instanceof NodeIdentifierWithPredicates nip) {
85 final QName qname = name.getNodeType();
86 if (nip.containsKey(qname)) {
87 writeQNameNode(MagnesiumNode.NODE_LEAF | MagnesiumNode.PREDICATE_ONE, qname);
88 stack.push(KEY_LEAF_STATE);
93 startSimpleNode(MagnesiumNode.NODE_LEAF, name);
97 public final void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
98 startQNameNode(MagnesiumNode.NODE_LEAFSET, name);
102 public final void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
103 startQNameNode(MagnesiumNode.NODE_LEAFSET_ORDERED, name);
107 public final void startLeafSetEntryNode(final NodeWithValue<?> name) throws IOException {
108 if (matchesParentQName(name.getNodeType())) {
109 output.writeByte(MagnesiumNode.NODE_LEAFSET_ENTRY);
110 stack.push(NO_ENDNODE_STATE);
112 startSimpleNode(MagnesiumNode.NODE_LEAFSET_ENTRY, name);
117 public final void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
118 startQNameNode(MagnesiumNode.NODE_CONTAINER, name);
122 public final void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
123 startQNameNode(MagnesiumNode.NODE_LIST, name);
127 public final void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
128 startInheritedNode(MagnesiumNode.NODE_LIST_ENTRY, name);
132 public final void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
133 startQNameNode(MagnesiumNode.NODE_MAP, name);
137 public final void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
139 final int size = identifier.size();
141 startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_ONE), identifier);
142 } else if (size == 0) {
143 startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_ZERO), identifier);
144 } else if (size < 256) {
145 startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_1B), identifier);
146 output.writeByte(size);
148 startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_4B), identifier);
149 output.writeInt(size);
152 writePredicates(identifier);
156 public final void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
157 startQNameNode(MagnesiumNode.NODE_MAP_ORDERED, name);
161 public final void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
162 startQNameNode(MagnesiumNode.NODE_CHOICE, name);
166 public final void startAugmentationNode(final AugmentationIdentifier identifier) throws IOException {
167 final Integer code = aidCodeMap.get(identifier);
169 aidCodeMap.put(identifier, aidCodeMap.size());
170 output.writeByte(MagnesiumNode.NODE_AUGMENTATION | MagnesiumNode.ADDR_DEFINE);
171 final Set<QName> qnames = identifier.getPossibleChildNames();
172 output.writeInt(qnames.size());
173 for (QName qname : qnames) {
174 writeQNameInternal(qname);
177 writeNodeType(MagnesiumNode.NODE_AUGMENTATION, code);
179 stack.push(identifier);
183 public final boolean startAnyxmlNode(final NodeIdentifier name, final Class<?> objectModel) throws IOException {
184 if (DOMSource.class.isAssignableFrom(objectModel)) {
185 startSimpleNode(MagnesiumNode.NODE_ANYXML, name);
192 public final void domSourceValue(final DOMSource value) throws IOException {
193 final StringWriter writer = new StringWriter();
195 TF.newTransformer().transform(value, new StreamResult(writer));
196 } catch (TransformerException e) {
197 throw new IOException("Error writing anyXml", e);
199 writeValue(writer.toString());
203 public final void endNode() throws IOException {
204 if (stack.pop() instanceof PathArgument) {
205 output.writeByte(MagnesiumNode.NODE_END);
210 public final void scalarValue(final Object value) throws IOException {
211 if (KEY_LEAF_STATE.equals(stack.peek())) {
212 LOG.trace("Inside a map entry key leaf, not emitting value {}", value);
219 final void writeQNameInternal(final QName qname) throws IOException {
220 final Integer code = qnameCodeMap.get(qname);
222 output.writeByte(MagnesiumValue.QNAME);
230 final void writePathArgumentInternal(final PathArgument pathArgument) throws IOException {
231 if (pathArgument instanceof NodeIdentifier nid) {
232 writeNodeIdentifier(nid);
233 } else if (pathArgument instanceof NodeIdentifierWithPredicates nip) {
234 writeNodeIdentifierWithPredicates(nip);
235 } else if (pathArgument instanceof AugmentationIdentifier augid) {
236 writeAugmentationIdentifier(augid);
237 } else if (pathArgument instanceof NodeWithValue<?> niv) {
238 writeNodeWithValue(niv);
240 throw new IOException("Unhandled PathArgument " + pathArgument);
244 private void writeAugmentationIdentifier(final AugmentationIdentifier identifier) throws IOException {
245 final Set<QName> qnames = identifier.getPossibleChildNames();
246 final int size = qnames.size();
248 output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER
249 | size << MagnesiumPathArgument.AID_COUNT_SHIFT);
250 } else if (size < 256) {
251 output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER | MagnesiumPathArgument.AID_COUNT_1B);
252 output.writeByte(size);
253 } else if (size < 65536) {
254 output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER | MagnesiumPathArgument.AID_COUNT_2B);
255 output.writeShort(size);
257 output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER | MagnesiumPathArgument.AID_COUNT_4B);
258 output.writeInt(size);
261 for (QName qname : qnames) {
262 writeQNameInternal(qname);
266 private void writeNodeIdentifier(final NodeIdentifier identifier) throws IOException {
267 writePathArgumentQName(identifier.getNodeType(), MagnesiumPathArgument.NODE_IDENTIFIER);
270 private void writeNodeIdentifierWithPredicates(final NodeIdentifierWithPredicates identifier) throws IOException {
271 final int size = identifier.size();
273 writePathArgumentQName(identifier.getNodeType(),
274 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES
275 | size << MagnesiumPathArgument.SIZE_SHIFT));
276 } else if (size < 256) {
277 writePathArgumentQName(identifier.getNodeType(),
278 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_1B));
279 output.writeByte(size);
280 } else if (size < 65536) {
281 writePathArgumentQName(identifier.getNodeType(),
282 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_2B));
283 output.writeShort(size);
285 writePathArgumentQName(identifier.getNodeType(),
286 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_4B));
287 output.writeInt(size);
290 writePredicates(identifier);
293 private void writePredicates(final NodeIdentifierWithPredicates identifier) throws IOException {
294 for (Entry<QName, Object> e : identifier.entrySet()) {
295 writeQNameInternal(e.getKey());
296 writeObject(e.getValue());
300 private void writeNodeWithValue(final NodeWithValue<?> identifier) throws IOException {
301 writePathArgumentQName(identifier.getNodeType(), MagnesiumPathArgument.NODE_WITH_VALUE);
302 writeObject(identifier.getValue());
305 private void writePathArgumentQName(final QName qname, final byte typeHeader) throws IOException {
306 final Integer code = qnameCodeMap.get(qname);
308 final int val = code;
310 output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_1B);
311 output.writeByte(val);
312 } else if (val < 65792) {
313 output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_2B);
314 output.writeShort(val - 256);
316 output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_4B);
317 output.writeInt(val);
320 // implied '| MagnesiumPathArgument.QNAME_DEF'
321 output.writeByte(typeHeader);
327 final 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());
365 } else if (value instanceof BigInteger val) {
368 throw new IOException("Unhandled value type " + value.getClass());
372 private void writeValue(final boolean value) throws IOException {
373 output.writeByte(value ? MagnesiumValue.BOOLEAN_TRUE : MagnesiumValue.BOOLEAN_FALSE);
376 private void writeValue(final byte value) throws IOException {
378 output.writeByte(MagnesiumValue.INT8);
379 output.writeByte(value);
381 output.writeByte(MagnesiumValue.INT8_0);
385 private void writeValue(final short value) throws IOException {
387 output.writeByte(MagnesiumValue.INT16);
388 output.writeShort(value);
390 output.writeByte(MagnesiumValue.INT16_0);
394 private void writeValue(final int value) throws IOException {
395 if ((value & 0xFFFF0000) != 0) {
396 output.writeByte(MagnesiumValue.INT32);
397 output.writeInt(value);
398 } else if (value != 0) {
399 output.writeByte(MagnesiumValue.INT32_2B);
400 output.writeShort(value);
402 output.writeByte(MagnesiumValue.INT32_0);
406 private void writeValue(final long value) throws IOException {
407 if ((value & 0xFFFFFFFF00000000L) != 0) {
408 output.writeByte(MagnesiumValue.INT64);
409 output.writeLong(value);
410 } else if (value != 0) {
411 output.writeByte(MagnesiumValue.INT64_4B);
412 output.writeInt((int) value);
414 output.writeByte(MagnesiumValue.INT64_0);
418 private void writeValue(final Uint8 value) throws IOException {
419 final byte b = value.byteValue();
421 output.writeByte(MagnesiumValue.UINT8);
424 output.writeByte(MagnesiumValue.UINT8_0);
428 private void writeValue(final Uint16 value) throws IOException {
429 final short s = value.shortValue();
431 output.writeByte(MagnesiumValue.UINT16);
432 output.writeShort(s);
434 output.writeByte(MagnesiumValue.UINT16_0);
438 private void writeValue(final Uint32 value) throws IOException {
439 final int i = value.intValue();
440 if ((i & 0xFFFF0000) != 0) {
441 output.writeByte(MagnesiumValue.UINT32);
444 output.writeByte(MagnesiumValue.UINT32_2B);
445 output.writeShort(i);
447 output.writeByte(MagnesiumValue.UINT32_0);
451 private void writeValue(final Uint64 value) throws IOException {
452 final long l = value.longValue();
453 if ((l & 0xFFFFFFFF00000000L) != 0) {
454 output.writeByte(MagnesiumValue.UINT64);
457 output.writeByte(MagnesiumValue.UINT64_4B);
458 output.writeInt((int) l);
460 output.writeByte(MagnesiumValue.UINT64_0);
464 abstract void writeValue(BigInteger value) throws IOException;
466 private void writeValue(final String value) throws IOException {
467 if (value.isEmpty()) {
468 output.writeByte(MagnesiumValue.STRING_EMPTY);
469 } else if (value.length() <= Short.MAX_VALUE / 2) {
470 output.writeByte(MagnesiumValue.STRING_UTF);
471 output.writeUTF(value);
472 } else if (value.length() <= 1048576) {
473 final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
474 if (bytes.length < 65536) {
475 output.writeByte(MagnesiumValue.STRING_2B);
476 output.writeShort(bytes.length);
478 output.writeByte(MagnesiumValue.STRING_4B);
479 output.writeInt(bytes.length);
483 output.writeByte(MagnesiumValue.STRING_CHARS);
484 output.writeInt(value.length());
485 output.writeChars(value);
489 private void writeValue(final byte[] value) throws IOException {
490 if (value.length < 128) {
491 output.writeByte(MagnesiumValue.BINARY_0 + value.length);
492 } else if (value.length < 384) {
493 output.writeByte(MagnesiumValue.BINARY_1B);
494 output.writeByte(value.length - 128);
495 } else if (value.length < 65920) {
496 output.writeByte(MagnesiumValue.BINARY_2B);
497 output.writeShort(value.length - 384);
499 output.writeByte(MagnesiumValue.BINARY_4B);
500 output.writeInt(value.length);
505 private void writeValue(final YangInstanceIdentifier value) throws IOException {
506 final List<PathArgument> args = value.getPathArguments();
507 final int size = args.size();
509 output.writeByte(MagnesiumValue.YIID);
510 output.writeInt(size);
512 output.writeByte(MagnesiumValue.YIID_0 + size);
514 for (PathArgument arg : args) {
515 writePathArgumentInternal(arg);
519 private void writeValue(final Set<?> value) throws IOException {
520 final int size = value.size();
522 output.writeByte(MagnesiumValue.BITS_0 + size);
523 } else if (size < 285) {
524 output.writeByte(MagnesiumValue.BITS_1B);
525 output.writeByte(size - 29);
526 } else if (size < 65821) {
527 output.writeByte(MagnesiumValue.BITS_2B);
528 output.writeShort(size - 285);
530 output.writeByte(MagnesiumValue.BITS_4B);
531 output.writeInt(size);
534 for (Object bit : value) {
535 checkArgument(bit instanceof String, "Expected value type to be String but was %s", bit);
536 encodeString((String) bit);
540 // Check if the proposed QName matches the parent. This is only effective if the parent is identified by
541 // NodeIdentifier -- which is typically true
542 private boolean matchesParentQName(final QName qname) {
543 final Object current = stack.peek();
544 return current instanceof NodeIdentifier nid && qname.equals(nid.getNodeType());
547 // Start an END_NODE-terminated node, which typically has a QName matching the parent. If that is the case we emit
548 // a parent reference instead of an explicit QName reference -- saving at least one byte
549 private void startInheritedNode(final byte type, final PathArgument name) throws IOException {
550 final QName qname = name.getNodeType();
551 if (matchesParentQName(qname)) {
554 writeQNameNode(type, qname);
559 // Start an END_NODE-terminated node, which needs its QName encoded
560 private void startQNameNode(final byte type, final PathArgument name) throws IOException {
561 writeQNameNode(type, name.getNodeType());
565 // Start a simple node, which is not terminated through END_NODE and encode its QName
566 private void startSimpleNode(final byte type, final PathArgument name) throws IOException {
567 writeQNameNode(type, name.getNodeType());
568 stack.push(NO_ENDNODE_STATE);
571 // Encode a QName-based (i.e. NodeIdentifier*) node with a particular QName. This will either result in a QName
572 // definition, or a reference, where this is encoded along with the node type.
573 private void writeQNameNode(final int type, final @NonNull QName qname) throws IOException {
574 final Integer code = qnameCodeMap.get(qname);
576 output.writeByte(type | MagnesiumNode.ADDR_DEFINE);
579 writeNodeType(type, code);
583 // Write a node type + lookup
584 private void writeNodeType(final int type, final int code) throws IOException {
586 output.writeByte(type | MagnesiumNode.ADDR_LOOKUP_1B);
587 output.writeByte(code);
589 output.writeByte(type | MagnesiumNode.ADDR_LOOKUP_4B);
590 output.writeInt(code);
594 // Encode a QName using lookup tables, resuling either in a reference to an existing entry, or emitting two
596 private void encodeQName(final @NonNull QName qname) throws IOException {
597 final Integer prev = qnameCodeMap.put(qname, qnameCodeMap.size());
599 throw new IOException("Internal coding error: attempted to re-encode " + qname + "%s already encoded as "
603 final QNameModule module = qname.getModule();
604 final Integer code = moduleCodeMap.get(module);
606 moduleCodeMap.put(module, moduleCodeMap.size());
607 encodeString(module.getNamespace().toString());
608 final Optional<Revision> rev = module.getRevision();
609 if (rev.isPresent()) {
610 encodeString(rev.orElseThrow().toString());
612 output.writeByte(MagnesiumValue.STRING_EMPTY);
615 writeModuleRef(code);
617 encodeString(qname.getLocalName());
620 // Encode a String using lookup tables, resulting either in a reference to an existing entry, or emitting as
622 private void encodeString(final @NonNull String str) throws IOException {
623 final Integer code = stringCodeMap.get(str);
627 stringCodeMap.put(str, stringCodeMap.size());
632 // Write a QName with a lookup table reference. This is a combination of asserting the value is a QName plus
633 // the effects of writeRef()
634 private void writeQNameRef(final int code) throws IOException {
635 final int val = code;
637 output.writeByte(MagnesiumValue.QNAME_REF_1B);
638 output.writeByte(val);
639 } else if (val < 65792) {
640 output.writeByte(MagnesiumValue.QNAME_REF_2B);
641 output.writeShort(val - 256);
643 output.writeByte(MagnesiumValue.QNAME_REF_4B);
644 output.writeInt(val);
648 // Write a lookup table reference, which table is being referenced is implied by the caller
649 private void writeRef(final int code) throws IOException {
650 final int val = code;
652 output.writeByte(MagnesiumValue.STRING_REF_1B);
653 output.writeByte(val);
654 } else if (val < 65792) {
655 output.writeByte(MagnesiumValue.STRING_REF_2B);
656 output.writeShort(val - 256);
658 output.writeByte(MagnesiumValue.STRING_REF_4B);
659 output.writeInt(val);
663 // Write a lookup module table reference, which table is being referenced is implied by the caller
664 private void writeModuleRef(final int code) throws IOException {
665 final int val = code;
667 output.writeByte(MagnesiumValue.MODREF_1B);
668 output.writeByte(val);
669 } else if (val < 65792) {
670 output.writeByte(MagnesiumValue.MODREF_2B);
671 output.writeShort(val - 256);
673 output.writeByte(MagnesiumValue.MODREF_4B);
674 output.writeInt(val);