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.NodeIdentifier;
41 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
42 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
43 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 * Abstract base class for NormalizedNodeDataOutput based on {@link MagnesiumNode}, {@link MagnesiumPathArgument} and
49 * {@link MagnesiumValue}.
51 final class MagnesiumDataOutput extends AbstractNormalizedNodeDataOutput {
52 private static final Logger LOG = LoggerFactory.getLogger(MagnesiumDataOutput.class);
54 // Marker for encoding state when we have entered startLeafNode() within a startMapEntry() and that leaf corresponds
55 // to a key carried within NodeIdentifierWithPredicates.
56 private static final Object KEY_LEAF_STATE = new Object();
57 // Marker for nodes which have simple content and do not use END_NODE marker to terminate
58 private static final Object NO_ENDNODE_STATE = new Object();
60 private static final TransformerFactory TF = TransformerFactory.newInstance();
63 * Stack tracking encoding state. In general we track the node identifier of the currently-open element, but there
64 * are a few other circumstances where we push other objects. See {@link #KEY_LEAF_STATE} and
65 * {@link #NO_ENDNODE_STATE}.
67 private final Deque<Object> stack = new ArrayDeque<>();
70 private final Map<QNameModule, Integer> moduleCodeMap = new HashMap<>();
71 private final Map<String, Integer> stringCodeMap = new HashMap<>();
72 private final Map<QName, Integer> qnameCodeMap = new HashMap<>();
74 MagnesiumDataOutput(final DataOutput output) {
79 public void startLeafNode(final NodeIdentifier name) throws IOException {
80 final Object current = stack.peek();
81 if (current instanceof NodeIdentifierWithPredicates nip) {
82 final QName qname = name.getNodeType();
83 if (nip.containsKey(qname)) {
84 writeQNameNode(MagnesiumNode.NODE_LEAF | MagnesiumNode.PREDICATE_ONE, qname);
85 stack.push(KEY_LEAF_STATE);
90 startSimpleNode(MagnesiumNode.NODE_LEAF, name);
94 public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
95 startQNameNode(MagnesiumNode.NODE_LEAFSET, name);
99 public void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
100 startQNameNode(MagnesiumNode.NODE_LEAFSET_ORDERED, name);
104 public void startLeafSetEntryNode(final NodeWithValue<?> name) throws IOException {
105 if (matchesParentQName(name.getNodeType())) {
106 output.writeByte(MagnesiumNode.NODE_LEAFSET_ENTRY);
107 stack.push(NO_ENDNODE_STATE);
109 startSimpleNode(MagnesiumNode.NODE_LEAFSET_ENTRY, name);
114 public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
115 startQNameNode(MagnesiumNode.NODE_CONTAINER, name);
119 public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
120 startQNameNode(MagnesiumNode.NODE_LIST, name);
124 public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
125 startInheritedNode(MagnesiumNode.NODE_LIST_ENTRY, name);
129 public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
130 startQNameNode(MagnesiumNode.NODE_MAP, name);
134 public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
136 final int size = identifier.size();
138 startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_ONE), identifier);
139 } else if (size == 0) {
140 startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_ZERO), identifier);
141 } else if (size < 256) {
142 startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_1B), identifier);
143 output.writeByte(size);
145 startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_4B), identifier);
146 output.writeInt(size);
149 writePredicates(identifier);
153 public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
154 startQNameNode(MagnesiumNode.NODE_MAP_ORDERED, name);
158 public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
159 startQNameNode(MagnesiumNode.NODE_CHOICE, name);
163 public boolean startAnyxmlNode(final NodeIdentifier name, final Class<?> objectModel) throws IOException {
164 if (DOMSource.class.isAssignableFrom(objectModel)) {
165 startSimpleNode(MagnesiumNode.NODE_ANYXML, name);
172 public void domSourceValue(final DOMSource value) throws IOException {
173 final StringWriter writer = new StringWriter();
175 TF.newTransformer().transform(value, new StreamResult(writer));
176 } catch (TransformerException e) {
177 throw new IOException("Error writing anyXml", e);
179 writeValue(writer.toString());
183 public void endNode() throws IOException {
184 if (stack.pop() instanceof PathArgument) {
185 output.writeByte(MagnesiumNode.NODE_END);
190 public void scalarValue(final Object value) throws IOException {
191 if (KEY_LEAF_STATE.equals(stack.peek())) {
192 LOG.trace("Inside a map entry key leaf, not emitting value {}", value);
198 @Override short streamVersion() {
199 return TokenTypes.MAGNESIUM_VERSION;
202 @Override void writeQNameInternal(final QName qname) throws IOException {
203 final Integer code = qnameCodeMap.get(qname);
205 output.writeByte(MagnesiumValue.QNAME);
212 @Override void writePathArgumentInternal(final PathArgument pathArgument) throws IOException {
213 if (pathArgument instanceof NodeIdentifier nid) {
214 writeNodeIdentifier(nid);
215 } else if (pathArgument instanceof NodeIdentifierWithPredicates nip) {
216 writeNodeIdentifierWithPredicates(nip);
217 } else if (pathArgument instanceof NodeWithValue<?> niv) {
218 writeNodeWithValue(niv);
220 throw new IOException("Unhandled PathArgument " + pathArgument);
224 private void writeNodeIdentifier(final NodeIdentifier identifier) throws IOException {
225 writePathArgumentQName(identifier.getNodeType(), MagnesiumPathArgument.NODE_IDENTIFIER);
228 private void writeNodeIdentifierWithPredicates(final NodeIdentifierWithPredicates identifier) throws IOException {
229 final int size = identifier.size();
231 writePathArgumentQName(identifier.getNodeType(),
232 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES
233 | size << MagnesiumPathArgument.SIZE_SHIFT));
234 } else if (size < 256) {
235 writePathArgumentQName(identifier.getNodeType(),
236 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_1B));
237 output.writeByte(size);
238 } else if (size < 65536) {
239 writePathArgumentQName(identifier.getNodeType(),
240 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_2B));
241 output.writeShort(size);
243 writePathArgumentQName(identifier.getNodeType(),
244 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_4B));
245 output.writeInt(size);
248 writePredicates(identifier);
251 private void writePredicates(final NodeIdentifierWithPredicates identifier) throws IOException {
252 for (Entry<QName, Object> e : identifier.entrySet()) {
253 writeQNameInternal(e.getKey());
254 writeObject(e.getValue());
258 private void writeNodeWithValue(final NodeWithValue<?> identifier) throws IOException {
259 writePathArgumentQName(identifier.getNodeType(), MagnesiumPathArgument.NODE_WITH_VALUE);
260 writeObject(identifier.getValue());
263 private void writePathArgumentQName(final QName qname, final byte typeHeader) throws IOException {
264 final Integer code = qnameCodeMap.get(qname);
266 final int val = code;
268 output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_1B);
269 output.writeByte(val);
270 } else if (val < 65792) {
271 output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_2B);
272 output.writeShort(val - 256);
274 output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_4B);
275 output.writeInt(val);
278 // implied '| MagnesiumPathArgument.QNAME_DEF'
279 output.writeByte(typeHeader);
284 @Override void writeYangInstanceIdentifierInternal(final YangInstanceIdentifier identifier) throws IOException {
285 writeValue(identifier);
288 private void writeObject(final @NonNull Object value) throws IOException {
289 if (value instanceof String str) {
291 } else if (value instanceof Boolean bool) {
293 } else if (value instanceof Byte byteVal) {
295 } else if (value instanceof Short shortVal) {
296 writeValue(shortVal);
297 } else if (value instanceof Integer intVal) {
299 } else if (value instanceof Long longVal) {
301 } else if (value instanceof Uint8 uint8) {
303 } else if (value instanceof Uint16 uint16) {
305 } else if (value instanceof Uint32 uint32) {
307 } else if (value instanceof Uint64 uint64) {
309 } else if (value instanceof QName qname) {
310 writeQNameInternal(qname);
311 } else if (value instanceof YangInstanceIdentifier id) {
313 } else if (value instanceof byte[] bytes) {
315 } else if (value instanceof Empty) {
316 output.writeByte(MagnesiumValue.EMPTY);
317 } else if (value instanceof Set<?> set) {
319 } else if (value instanceof BigDecimal || value instanceof Decimal64) {
320 output.writeByte(MagnesiumValue.BIGDECIMAL);
321 output.writeUTF(value.toString());
323 throw new IOException("Unhandled value type " + value.getClass());
327 private void writeValue(final boolean value) throws IOException {
328 output.writeByte(value ? MagnesiumValue.BOOLEAN_TRUE : MagnesiumValue.BOOLEAN_FALSE);
331 private void writeValue(final byte value) throws IOException {
333 output.writeByte(MagnesiumValue.INT8);
334 output.writeByte(value);
336 output.writeByte(MagnesiumValue.INT8_0);
340 private void writeValue(final short value) throws IOException {
342 output.writeByte(MagnesiumValue.INT16);
343 output.writeShort(value);
345 output.writeByte(MagnesiumValue.INT16_0);
349 private void writeValue(final int value) throws IOException {
350 if ((value & 0xFFFF0000) != 0) {
351 output.writeByte(MagnesiumValue.INT32);
352 output.writeInt(value);
353 } else if (value != 0) {
354 output.writeByte(MagnesiumValue.INT32_2B);
355 output.writeShort(value);
357 output.writeByte(MagnesiumValue.INT32_0);
361 private void writeValue(final long value) throws IOException {
362 if ((value & 0xFFFFFFFF00000000L) != 0) {
363 output.writeByte(MagnesiumValue.INT64);
364 output.writeLong(value);
365 } else if (value != 0) {
366 output.writeByte(MagnesiumValue.INT64_4B);
367 output.writeInt((int) value);
369 output.writeByte(MagnesiumValue.INT64_0);
373 private void writeValue(final Uint8 value) throws IOException {
374 final byte b = value.byteValue();
376 output.writeByte(MagnesiumValue.UINT8);
379 output.writeByte(MagnesiumValue.UINT8_0);
383 private void writeValue(final Uint16 value) throws IOException {
384 final short s = value.shortValue();
386 output.writeByte(MagnesiumValue.UINT16);
387 output.writeShort(s);
389 output.writeByte(MagnesiumValue.UINT16_0);
393 private void writeValue(final Uint32 value) throws IOException {
394 final int i = value.intValue();
395 if ((i & 0xFFFF0000) != 0) {
396 output.writeByte(MagnesiumValue.UINT32);
399 output.writeByte(MagnesiumValue.UINT32_2B);
400 output.writeShort(i);
402 output.writeByte(MagnesiumValue.UINT32_0);
406 private void writeValue(final Uint64 value) throws IOException {
407 final long l = value.longValue();
408 if ((l & 0xFFFFFFFF00000000L) != 0) {
409 output.writeByte(MagnesiumValue.UINT64);
412 output.writeByte(MagnesiumValue.UINT64_4B);
413 output.writeInt((int) l);
415 output.writeByte(MagnesiumValue.UINT64_0);
419 private void writeValue(final String value) throws IOException {
420 if (value.isEmpty()) {
421 output.writeByte(MagnesiumValue.STRING_EMPTY);
422 } else if (value.length() <= Short.MAX_VALUE / 2) {
423 output.writeByte(MagnesiumValue.STRING_UTF);
424 output.writeUTF(value);
425 } else if (value.length() <= 1048576) {
426 final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
427 if (bytes.length < 65536) {
428 output.writeByte(MagnesiumValue.STRING_2B);
429 output.writeShort(bytes.length);
431 output.writeByte(MagnesiumValue.STRING_4B);
432 output.writeInt(bytes.length);
436 output.writeByte(MagnesiumValue.STRING_CHARS);
437 output.writeInt(value.length());
438 output.writeChars(value);
442 private void writeValue(final byte[] value) throws IOException {
443 if (value.length < 128) {
444 output.writeByte(MagnesiumValue.BINARY_0 + value.length);
445 } else if (value.length < 384) {
446 output.writeByte(MagnesiumValue.BINARY_1B);
447 output.writeByte(value.length - 128);
448 } else if (value.length < 65920) {
449 output.writeByte(MagnesiumValue.BINARY_2B);
450 output.writeShort(value.length - 384);
452 output.writeByte(MagnesiumValue.BINARY_4B);
453 output.writeInt(value.length);
458 private void writeValue(final YangInstanceIdentifier value) throws IOException {
459 final List<PathArgument> args = value.getPathArguments();
460 final int size = args.size();
462 output.writeByte(MagnesiumValue.YIID);
463 output.writeInt(size);
465 output.writeByte(MagnesiumValue.YIID_0 + size);
467 for (PathArgument arg : args) {
468 writePathArgumentInternal(arg);
472 private void writeValue(final Set<?> value) throws IOException {
473 final int size = value.size();
475 output.writeByte(MagnesiumValue.BITS_0 + size);
476 } else if (size < 285) {
477 output.writeByte(MagnesiumValue.BITS_1B);
478 output.writeByte(size - 29);
479 } else if (size < 65821) {
480 output.writeByte(MagnesiumValue.BITS_2B);
481 output.writeShort(size - 285);
483 output.writeByte(MagnesiumValue.BITS_4B);
484 output.writeInt(size);
487 for (Object bit : value) {
488 checkArgument(bit instanceof String, "Expected value type to be String but was %s", bit);
489 encodeString((String) bit);
493 // Check if the proposed QName matches the parent. This is only effective if the parent is identified by
494 // NodeIdentifier -- which is typically true
495 private boolean matchesParentQName(final QName qname) {
496 final Object current = stack.peek();
497 return current instanceof NodeIdentifier nid && qname.equals(nid.getNodeType());
500 // Start an END_NODE-terminated node, which typically has a QName matching the parent. If that is the case we emit
501 // a parent reference instead of an explicit QName reference -- saving at least one byte
502 private void startInheritedNode(final byte type, final PathArgument name) throws IOException {
503 final QName qname = name.getNodeType();
504 if (matchesParentQName(qname)) {
507 writeQNameNode(type, qname);
512 // Start an END_NODE-terminated node, which needs its QName encoded
513 private void startQNameNode(final byte type, final PathArgument name) throws IOException {
514 writeQNameNode(type, name.getNodeType());
518 // Start a simple node, which is not terminated through END_NODE and encode its QName
519 private void startSimpleNode(final byte type, final PathArgument name) throws IOException {
520 writeQNameNode(type, name.getNodeType());
521 stack.push(NO_ENDNODE_STATE);
524 // Encode a QName-based (i.e. NodeIdentifier*) node with a particular QName. This will either result in a QName
525 // definition, or a reference, where this is encoded along with the node type.
526 private void writeQNameNode(final int type, final @NonNull QName qname) throws IOException {
527 final Integer code = qnameCodeMap.get(qname);
529 output.writeByte(type | MagnesiumNode.ADDR_DEFINE);
532 writeNodeType(type, code);
536 // Write a node type + lookup
537 private void writeNodeType(final int type, final int code) throws IOException {
539 output.writeByte(type | MagnesiumNode.ADDR_LOOKUP_1B);
540 output.writeByte(code);
542 output.writeByte(type | MagnesiumNode.ADDR_LOOKUP_4B);
543 output.writeInt(code);
547 // Encode a QName using lookup tables, resuling either in a reference to an existing entry, or emitting two
549 private void encodeQName(final @NonNull QName qname) throws IOException {
550 final Integer prev = qnameCodeMap.put(qname, qnameCodeMap.size());
552 throw new IOException("Internal coding error: attempted to re-encode " + qname + "%s already encoded as "
556 final QNameModule module = qname.getModule();
557 final Integer code = moduleCodeMap.get(module);
559 moduleCodeMap.put(module, moduleCodeMap.size());
560 encodeString(module.getNamespace().toString());
561 final Optional<Revision> rev = module.getRevision();
562 if (rev.isPresent()) {
563 encodeString(rev.orElseThrow().toString());
565 output.writeByte(MagnesiumValue.STRING_EMPTY);
568 writeModuleRef(code);
570 encodeString(qname.getLocalName());
573 // Encode a String using lookup tables, resulting either in a reference to an existing entry, or emitting as
575 private void encodeString(final @NonNull String str) throws IOException {
576 final Integer code = stringCodeMap.get(str);
580 stringCodeMap.put(str, stringCodeMap.size());
585 // Write a QName with a lookup table reference. This is a combination of asserting the value is a QName plus
586 // the effects of writeRef()
587 private void writeQNameRef(final int code) throws IOException {
588 final int val = code;
590 output.writeByte(MagnesiumValue.QNAME_REF_1B);
591 output.writeByte(val);
592 } else if (val < 65792) {
593 output.writeByte(MagnesiumValue.QNAME_REF_2B);
594 output.writeShort(val - 256);
596 output.writeByte(MagnesiumValue.QNAME_REF_4B);
597 output.writeInt(val);
601 // Write a lookup table reference, which table is being referenced is implied by the caller
602 private void writeRef(final int code) throws IOException {
603 final int val = code;
605 output.writeByte(MagnesiumValue.STRING_REF_1B);
606 output.writeByte(val);
607 } else if (val < 65792) {
608 output.writeByte(MagnesiumValue.STRING_REF_2B);
609 output.writeShort(val - 256);
611 output.writeByte(MagnesiumValue.STRING_REF_4B);
612 output.writeInt(val);
616 // Write a lookup module table reference, which table is being referenced is implied by the caller
617 private void writeModuleRef(final int code) throws IOException {
618 final int val = code;
620 output.writeByte(MagnesiumValue.MODREF_1B);
621 output.writeByte(val);
622 } else if (val < 65792) {
623 output.writeByte(MagnesiumValue.MODREF_2B);
624 output.writeShort(val - 256);
626 output.writeByte(MagnesiumValue.MODREF_4B);
627 output.writeInt(val);