Bump odlparent to 6.0.0
[controller.git] / opendaylight / md-sal / sal-clustering-commons / src / main / java / org / opendaylight / controller / cluster / datastore / node / utils / stream / AbstractMagnesiumDataOutput.java
1 /*
2  * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.cluster.datastore.node.utils.stream;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11
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;
22 import java.util.Map;
23 import java.util.Map.Entry;
24 import java.util.Optional;
25 import java.util.Set;
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.rfc8528.data.api.MountPointIdentifier;
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;
48
49 /**
50  * Abstract base class for NormalizedNodeDataOutput based on {@link MagnesiumNode}, {@link MagnesiumPathArgument} and
51  * {@link MagnesiumValue}.
52  */
53 abstract class AbstractMagnesiumDataOutput extends AbstractNormalizedNodeDataOutput {
54     private static final Logger LOG = LoggerFactory.getLogger(AbstractMagnesiumDataOutput.class);
55
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();
61
62     private static final TransformerFactory TF = TransformerFactory.newInstance();
63
64     /**
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}.
68      */
69     private final Deque<Object> stack = new ArrayDeque<>();
70
71     // Coding maps
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<>();
76
77     AbstractMagnesiumDataOutput(final DataOutput output) {
78         super(output);
79     }
80
81     @Override
82     public final void startLeafNode(final NodeIdentifier name) throws IOException {
83         final Object current = stack.peek();
84         if (current instanceof NodeIdentifierWithPredicates) {
85             final QName qname = name.getNodeType();
86             if (((NodeIdentifierWithPredicates) current).containsKey(qname)) {
87                 writeQNameNode(MagnesiumNode.NODE_LEAF | MagnesiumNode.PREDICATE_ONE, qname);
88                 stack.push(KEY_LEAF_STATE);
89                 return;
90             }
91         }
92
93         startSimpleNode(MagnesiumNode.NODE_LEAF, name);
94     }
95
96     @Override
97     public final void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
98         startQNameNode(MagnesiumNode.NODE_LEAFSET, name);
99     }
100
101     @Override
102     public final void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
103         startQNameNode(MagnesiumNode.NODE_LEAFSET_ORDERED, name);
104     }
105
106     @Override
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);
111         } else {
112             startSimpleNode(MagnesiumNode.NODE_LEAFSET_ENTRY, name);
113         }
114     }
115
116     @Override
117     public final void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
118         startQNameNode(MagnesiumNode.NODE_CONTAINER, name);
119     }
120
121     @Override
122     public final void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
123         startQNameNode(MagnesiumNode.NODE_LIST, name);
124     }
125
126     @Override
127     public final void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
128         startInheritedNode(MagnesiumNode.NODE_LIST_ENTRY, name);
129     }
130
131     @Override
132     public final void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
133         startQNameNode(MagnesiumNode.NODE_MAP, name);
134     }
135
136     @Override
137     public final void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
138             throws IOException {
139         final int size = identifier.size();
140         if (size == 1) {
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);
147         } else {
148             startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_4B), identifier);
149             output.writeInt(size);
150         }
151
152         writePredicates(identifier);
153     }
154
155     @Override
156     public final void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
157         startQNameNode(MagnesiumNode.NODE_MAP_ORDERED, name);
158     }
159
160     @Override
161     public final void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
162         startQNameNode(MagnesiumNode.NODE_CHOICE, name);
163     }
164
165     @Override
166     public final void startAugmentationNode(final AugmentationIdentifier identifier) throws IOException {
167         final Integer code = aidCodeMap.get(identifier);
168         if (code == null) {
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);
175             }
176         } else {
177             writeNodeType(MagnesiumNode.NODE_AUGMENTATION, code);
178         }
179         stack.push(identifier);
180     }
181
182     @Override
183     public final void startAnyxmlNode(final NodeIdentifier name) throws IOException {
184         startSimpleNode(MagnesiumNode.NODE_ANYXML, name);
185     }
186
187     @Override
188     public final void domSourceValue(final DOMSource value) throws IOException {
189         final StringWriter writer = new StringWriter();
190         try {
191             TF.newTransformer().transform(value, new StreamResult(writer));
192         } catch (TransformerException e) {
193             throw new IOException("Error writing anyXml", e);
194         }
195         writeValue(writer.toString());
196     }
197
198     @Override
199     public final void startYangModeledAnyXmlNode(final NodeIdentifier name, final int childSizeHint)
200             throws IOException {
201         // FIXME: implement this
202         throw new UnsupportedOperationException();
203     }
204
205     @Override
206     public final void endNode() throws IOException {
207         if (stack.pop() instanceof PathArgument) {
208             output.writeByte(MagnesiumNode.NODE_END);
209         }
210     }
211
212     @Override
213     public final void scalarValue(final Object value) throws IOException {
214         if (KEY_LEAF_STATE.equals(stack.peek())) {
215             LOG.trace("Inside a map entry key leaf, not emitting value {}", value);
216         } else {
217             writeObject(value);
218         }
219     }
220
221     @Override
222     final void writeQNameInternal(final QName qname) throws IOException {
223         final Integer code = qnameCodeMap.get(qname);
224         if (code == null) {
225             output.writeByte(MagnesiumValue.QNAME);
226             encodeQName(qname);
227         } else {
228             writeQNameRef(code);
229         }
230     }
231
232     @Override
233     final void writePathArgumentInternal(final PathArgument pathArgument) throws IOException {
234         if (pathArgument instanceof NodeIdentifier) {
235             writeNodeIdentifier((NodeIdentifier) pathArgument);
236         } else if (pathArgument instanceof NodeIdentifierWithPredicates) {
237             writeNodeIdentifierWithPredicates((NodeIdentifierWithPredicates) pathArgument);
238         } else if (pathArgument instanceof AugmentationIdentifier) {
239             writeAugmentationIdentifier((AugmentationIdentifier) pathArgument);
240         } else if (pathArgument instanceof NodeWithValue) {
241             writeNodeWithValue((NodeWithValue<?>) pathArgument);
242         } else if (pathArgument instanceof MountPointIdentifier) {
243             writeMountPointIdentifier((MountPointIdentifier) pathArgument);
244         } else {
245             throw new IOException("Unhandled PathArgument " + pathArgument);
246         }
247     }
248
249     private void writeAugmentationIdentifier(final AugmentationIdentifier identifier) throws IOException {
250         final Set<QName> qnames = identifier.getPossibleChildNames();
251         final int size = qnames.size();
252         if (size < 29) {
253             output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER
254                 | size << MagnesiumPathArgument.AID_COUNT_SHIFT);
255         } else if (size < 256) {
256             output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER | MagnesiumPathArgument.AID_COUNT_1B);
257             output.writeByte(size);
258         } else if (size < 65536) {
259             output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER | MagnesiumPathArgument.AID_COUNT_2B);
260             output.writeShort(size);
261         } else {
262             output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER | MagnesiumPathArgument.AID_COUNT_4B);
263             output.writeInt(size);
264         }
265
266         for (QName qname : qnames) {
267             writeQNameInternal(qname);
268         }
269     }
270
271     private void writeNodeIdentifier(final NodeIdentifier identifier) throws IOException {
272         writePathArgumentQName(identifier.getNodeType(), MagnesiumPathArgument.NODE_IDENTIFIER);
273     }
274
275     private void writeMountPointIdentifier(final MountPointIdentifier identifier) throws IOException {
276         writePathArgumentQName(identifier.getNodeType(), MagnesiumPathArgument.MOUNTPOINT_IDENTIFIER);
277     }
278
279     private void writeNodeIdentifierWithPredicates(final NodeIdentifierWithPredicates identifier) throws IOException {
280         final int size = identifier.size();
281         if (size < 5) {
282             writePathArgumentQName(identifier.getNodeType(),
283                 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES
284                         | size << MagnesiumPathArgument.SIZE_SHIFT));
285         } else if (size < 256) {
286             writePathArgumentQName(identifier.getNodeType(),
287                 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_1B));
288             output.writeByte(size);
289         } else if (size < 65536) {
290             writePathArgumentQName(identifier.getNodeType(),
291                 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_2B));
292             output.writeShort(size);
293         } else {
294             writePathArgumentQName(identifier.getNodeType(),
295                 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_4B));
296             output.writeInt(size);
297         }
298
299         writePredicates(identifier);
300     }
301
302     private void writePredicates(final NodeIdentifierWithPredicates identifier) throws IOException {
303         for (Entry<QName, Object> e : identifier.entrySet()) {
304             writeQNameInternal(e.getKey());
305             writeObject(e.getValue());
306         }
307     }
308
309     private void writeNodeWithValue(final NodeWithValue<?> identifier) throws IOException {
310         writePathArgumentQName(identifier.getNodeType(), MagnesiumPathArgument.NODE_WITH_VALUE);
311         writeObject(identifier.getValue());
312     }
313
314     private void writePathArgumentQName(final QName qname, final byte typeHeader) throws IOException {
315         final Integer code = qnameCodeMap.get(qname);
316         if (code != null) {
317             final int val = code;
318             if (val < 256) {
319                 output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_1B);
320                 output.writeByte(val);
321             } else if (val < 65792) {
322                 output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_2B);
323                 output.writeShort(val - 256);
324             } else {
325                 output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_4B);
326                 output.writeInt(val);
327             }
328         } else {
329             // implied '| MagnesiumPathArgument.QNAME_DEF'
330             output.writeByte(typeHeader);
331             encodeQName(qname);
332         }
333     }
334
335     @Override
336     final void writeYangInstanceIdentifierInternal(final YangInstanceIdentifier identifier) throws IOException {
337         writeValue(identifier);
338     }
339
340     private void writeObject(final @NonNull Object value) throws IOException {
341         if (value instanceof String) {
342             writeValue((String) value);
343         } else if (value instanceof Boolean) {
344             writeValue((Boolean) value);
345         } else if (value instanceof Byte) {
346             writeValue((Byte) value);
347         } else if (value instanceof Short) {
348             writeValue((Short) value);
349         } else if (value instanceof Integer) {
350             writeValue((Integer) value);
351         } else if (value instanceof Long) {
352             writeValue((Long) value);
353         } else if (value instanceof Uint8) {
354             writeValue((Uint8) value);
355         } else if (value instanceof Uint16) {
356             writeValue((Uint16) value);
357         } else if (value instanceof Uint32) {
358             writeValue((Uint32) value);
359         } else if (value instanceof Uint64) {
360             writeValue((Uint64) value);
361         } else if (value instanceof QName) {
362             writeQNameInternal((QName) value);
363         } else if (value instanceof YangInstanceIdentifier) {
364             writeValue((YangInstanceIdentifier) value);
365         } else if (value instanceof byte[]) {
366             writeValue((byte[]) value);
367         } else if (value instanceof Empty) {
368             output.writeByte(MagnesiumValue.EMPTY);
369         } else if (value instanceof Set) {
370             writeValue((Set<?>) value);
371         } else if (value instanceof BigDecimal) {
372             writeValue((BigDecimal) value);
373         } else if (value instanceof BigInteger) {
374             writeValue((BigInteger) value);
375         } else {
376             throw new IOException("Unhandled value type " + value.getClass());
377         }
378     }
379
380     private void writeValue(final boolean value) throws IOException {
381         output.writeByte(value ? MagnesiumValue.BOOLEAN_TRUE : MagnesiumValue.BOOLEAN_FALSE);
382     }
383
384     private void writeValue(final byte value) throws IOException {
385         if (value != 0) {
386             output.writeByte(MagnesiumValue.INT8);
387             output.writeByte(value);
388         } else {
389             output.writeByte(MagnesiumValue.INT8_0);
390         }
391     }
392
393     private void writeValue(final short value) throws IOException {
394         if (value != 0) {
395             output.writeByte(MagnesiumValue.INT16);
396             output.writeShort(value);
397         } else {
398             output.writeByte(MagnesiumValue.INT16_0);
399         }
400     }
401
402     private void writeValue(final int value) throws IOException {
403         if ((value & 0xFFFF0000) != 0) {
404             output.writeByte(MagnesiumValue.INT32);
405             output.writeInt(value);
406         } else if (value != 0) {
407             output.writeByte(MagnesiumValue.INT32_2B);
408             output.writeShort(value);
409         } else {
410             output.writeByte(MagnesiumValue.INT32_0);
411         }
412     }
413
414     private void writeValue(final long value) throws IOException {
415         if ((value & 0xFFFFFFFF00000000L) != 0) {
416             output.writeByte(MagnesiumValue.INT64);
417             output.writeLong(value);
418         } else if (value != 0) {
419             output.writeByte(MagnesiumValue.INT64_4B);
420             output.writeInt((int) value);
421         } else {
422             output.writeByte(MagnesiumValue.INT64_0);
423         }
424     }
425
426     private void writeValue(final Uint8 value) throws IOException {
427         final byte b = value.byteValue();
428         if (b != 0) {
429             output.writeByte(MagnesiumValue.UINT8);
430             output.writeByte(b);
431         } else {
432             output.writeByte(MagnesiumValue.UINT8_0);
433         }
434     }
435
436     private void writeValue(final Uint16 value) throws IOException {
437         final short s = value.shortValue();
438         if (s != 0) {
439             output.writeByte(MagnesiumValue.UINT16);
440             output.writeShort(s);
441         } else {
442             output.writeByte(MagnesiumValue.UINT16_0);
443         }
444     }
445
446     private void writeValue(final Uint32 value) throws IOException {
447         final int i = value.intValue();
448         if ((i & 0xFFFF0000) != 0) {
449             output.writeByte(MagnesiumValue.UINT32);
450             output.writeInt(i);
451         } else if (i != 0) {
452             output.writeByte(MagnesiumValue.UINT32_2B);
453             output.writeShort(i);
454         } else {
455             output.writeByte(MagnesiumValue.UINT32_0);
456         }
457     }
458
459     private void writeValue(final Uint64 value) throws IOException {
460         final long l = value.longValue();
461         if ((l & 0xFFFFFFFF00000000L) != 0) {
462             output.writeByte(MagnesiumValue.UINT64);
463             output.writeLong(l);
464         } else if (l != 0) {
465             output.writeByte(MagnesiumValue.UINT64_4B);
466             output.writeInt((int) l);
467         } else {
468             output.writeByte(MagnesiumValue.UINT64_0);
469         }
470     }
471
472     private void writeValue(final BigDecimal value) throws IOException {
473         output.writeByte(MagnesiumValue.BIGDECIMAL);
474         output.writeUTF(value.toString());
475     }
476
477     abstract void writeValue(BigInteger value) throws IOException;
478
479     private void writeValue(final String value) throws IOException {
480         if (value.isEmpty()) {
481             output.writeByte(MagnesiumValue.STRING_EMPTY);
482         } else if (value.length() <= Short.MAX_VALUE / 2) {
483             output.writeByte(MagnesiumValue.STRING_UTF);
484             output.writeUTF(value);
485         } else if (value.length() <= 1048576) {
486             final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
487             if (bytes.length < 65536) {
488                 output.writeByte(MagnesiumValue.STRING_2B);
489                 output.writeShort(bytes.length);
490             } else {
491                 output.writeByte(MagnesiumValue.STRING_4B);
492                 output.writeInt(bytes.length);
493             }
494             output.write(bytes);
495         } else {
496             output.writeByte(MagnesiumValue.STRING_CHARS);
497             output.writeInt(value.length());
498             output.writeChars(value);
499         }
500     }
501
502     private void writeValue(final byte[] value) throws IOException {
503         if (value.length < 128) {
504             output.writeByte(MagnesiumValue.BINARY_0 + value.length);
505         } else if (value.length < 384) {
506             output.writeByte(MagnesiumValue.BINARY_1B);
507             output.writeByte(value.length - 128);
508         } else if (value.length < 65920) {
509             output.writeByte(MagnesiumValue.BINARY_2B);
510             output.writeShort(value.length - 384);
511         } else {
512             output.writeByte(MagnesiumValue.BINARY_4B);
513             output.writeInt(value.length);
514         }
515         output.write(value);
516     }
517
518     private void writeValue(final YangInstanceIdentifier value) throws IOException {
519         final List<PathArgument> args = value.getPathArguments();
520         final int size = args.size();
521         if (size > 31) {
522             output.writeByte(MagnesiumValue.YIID);
523             output.writeInt(size);
524         } else {
525             output.writeByte(MagnesiumValue.YIID_0 + size);
526         }
527         for (PathArgument arg : args) {
528             writePathArgumentInternal(arg);
529         }
530     }
531
532     private void writeValue(final Set<?> value) throws IOException {
533         final int size = value.size();
534         if (size < 29) {
535             output.writeByte(MagnesiumValue.BITS_0 + size);
536         } else if (size < 285) {
537             output.writeByte(MagnesiumValue.BITS_1B);
538             output.writeByte(size - 29);
539         } else if (size < 65821) {
540             output.writeByte(MagnesiumValue.BITS_2B);
541             output.writeShort(size - 285);
542         } else {
543             output.writeByte(MagnesiumValue.BITS_4B);
544             output.writeInt(size);
545         }
546
547         for (Object bit : value) {
548             checkArgument(bit instanceof String, "Expected value type to be String but was %s", bit);
549             encodeString((String) bit);
550         }
551     }
552
553     // Check if the proposed QName matches the parent. This is only effective if the parent is identified by
554     // NodeIdentifier -- which is typically true
555     private boolean matchesParentQName(final QName qname) {
556         final Object current = stack.peek();
557         return current instanceof NodeIdentifier && qname.equals(((NodeIdentifier) current).getNodeType());
558     }
559
560     // Start an END_NODE-terminated node, which typically has a QName matching the parent. If that is the case we emit
561     // a parent reference instead of an explicit QName reference -- saving at least one byte
562     private void startInheritedNode(final byte type, final PathArgument name) throws IOException {
563         final QName qname = name.getNodeType();
564         if (matchesParentQName(qname)) {
565             output.write(type);
566         } else {
567             writeQNameNode(type, qname);
568         }
569         stack.push(name);
570     }
571
572     // Start an END_NODE-terminated node, which needs its QName encoded
573     private void startQNameNode(final byte type, final PathArgument name) throws IOException {
574         writeQNameNode(type, name.getNodeType());
575         stack.push(name);
576     }
577
578     // Start a simple node, which is not terminated through END_NODE and encode its QName
579     private void startSimpleNode(final byte type, final PathArgument name) throws IOException {
580         writeQNameNode(type, name.getNodeType());
581         stack.push(NO_ENDNODE_STATE);
582     }
583
584     // Encode a QName-based (i.e. NodeIdentifier*) node with a particular QName. This will either result in a QName
585     // definition, or a reference, where this is encoded along with the node type.
586     private void writeQNameNode(final int type, final @NonNull QName qname) throws IOException {
587         final Integer code = qnameCodeMap.get(qname);
588         if (code == null) {
589             output.writeByte(type | MagnesiumNode.ADDR_DEFINE);
590             encodeQName(qname);
591         } else {
592             writeNodeType(type, code);
593         }
594     }
595
596     // Write a node type + lookup
597     private void writeNodeType(final int type, final int code) throws IOException {
598         if (code <= 255) {
599             output.writeByte(type | MagnesiumNode.ADDR_LOOKUP_1B);
600             output.writeByte(code);
601         } else {
602             output.writeByte(type | MagnesiumNode.ADDR_LOOKUP_4B);
603             output.writeInt(code);
604         }
605     }
606
607     // Encode a QName using lookup tables, resuling either in a reference to an existing entry, or emitting two
608     // String values.
609     private void encodeQName(final @NonNull QName qname) throws IOException {
610         final Integer prev = qnameCodeMap.put(qname, qnameCodeMap.size());
611         if (prev != null) {
612             throw new IOException("Internal coding error: attempted to re-encode " + qname + "%s already encoded as "
613                     + prev);
614         }
615
616         final QNameModule module = qname.getModule();
617         final Integer code = moduleCodeMap.get(module);
618         if (code == null) {
619             moduleCodeMap.put(module, moduleCodeMap.size());
620             encodeString(module.getNamespace().toString());
621             final Optional<Revision> rev = module.getRevision();
622             if (rev.isPresent()) {
623                 encodeString(rev.get().toString());
624             } else {
625                 output.writeByte(MagnesiumValue.STRING_EMPTY);
626             }
627         } else {
628             writeModuleRef(code);
629         }
630         encodeString(qname.getLocalName());
631     }
632
633     // Encode a String using lookup tables, resulting either in a reference to an existing entry, or emitting as
634     // a literal value
635     private void encodeString(final @NonNull String str) throws IOException {
636         final Integer code = stringCodeMap.get(str);
637         if (code != null) {
638             writeRef(code);
639         } else {
640             stringCodeMap.put(str, stringCodeMap.size());
641             writeValue(str);
642         }
643     }
644
645     // Write a QName with a lookup table reference. This is a combination of asserting the value is a QName plus
646     // the effects of writeRef()
647     private void writeQNameRef(final int code) throws IOException {
648         final int val = code;
649         if (val < 256) {
650             output.writeByte(MagnesiumValue.QNAME_REF_1B);
651             output.writeByte(val);
652         } else if (val < 65792) {
653             output.writeByte(MagnesiumValue.QNAME_REF_2B);
654             output.writeShort(val - 256);
655         } else {
656             output.writeByte(MagnesiumValue.QNAME_REF_4B);
657             output.writeInt(val);
658         }
659     }
660
661     // Write a lookup table reference, which table is being referenced is implied by the caller
662     private void writeRef(final int code) throws IOException {
663         final int val = code;
664         if (val < 256) {
665             output.writeByte(MagnesiumValue.STRING_REF_1B);
666             output.writeByte(val);
667         } else if (val < 65792) {
668             output.writeByte(MagnesiumValue.STRING_REF_2B);
669             output.writeShort(val - 256);
670         } else {
671             output.writeByte(MagnesiumValue.STRING_REF_4B);
672             output.writeInt(val);
673         }
674     }
675
676     // Write a lookup module table reference, which table is being referenced is implied by the caller
677     private void writeModuleRef(final int code) throws IOException {
678         final int val = code;
679         if (val < 256) {
680             output.writeByte(MagnesiumValue.MODREF_1B);
681             output.writeByte(val);
682         } else if (val < 65792) {
683             output.writeByte(MagnesiumValue.MODREF_2B);
684             output.writeShort(val - 256);
685         } else {
686             output.writeByte(MagnesiumValue.MODREF_4B);
687             output.writeInt(val);
688         }
689     }
690 }