Remove MountPointIdentifier
[yangtools.git] / codec / yang-data-codec-binfmt / src / main / java / org / opendaylight / yangtools / yang / data / codec / binfmt / 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.yangtools.yang.data.codec.binfmt;
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.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;
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 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);
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 boolean startAnyxmlNode(final NodeIdentifier name, final Class<?> objectModel) throws IOException {
184         if (DOMSource.class.isAssignableFrom(objectModel)) {
185             startSimpleNode(MagnesiumNode.NODE_ANYXML, name);
186             return true;
187         }
188         return false;
189     }
190
191     @Override
192     public final void domSourceValue(final DOMSource value) throws IOException {
193         final StringWriter writer = new StringWriter();
194         try {
195             TF.newTransformer().transform(value, new StreamResult(writer));
196         } catch (TransformerException e) {
197             throw new IOException("Error writing anyXml", e);
198         }
199         writeValue(writer.toString());
200     }
201
202     @Override
203     public final void endNode() throws IOException {
204         if (stack.pop() instanceof PathArgument) {
205             output.writeByte(MagnesiumNode.NODE_END);
206         }
207     }
208
209     @Override
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);
213         } else {
214             writeObject(value);
215         }
216     }
217
218     @Override
219     final void writeQNameInternal(final QName qname) throws IOException {
220         final Integer code = qnameCodeMap.get(qname);
221         if (code == null) {
222             output.writeByte(MagnesiumValue.QNAME);
223             encodeQName(qname);
224         } else {
225             writeQNameRef(code);
226         }
227     }
228
229     @Override
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);
239         } else {
240             throw new IOException("Unhandled PathArgument " + pathArgument);
241         }
242     }
243
244     private void writeAugmentationIdentifier(final AugmentationIdentifier identifier) throws IOException {
245         final Set<QName> qnames = identifier.getPossibleChildNames();
246         final int size = qnames.size();
247         if (size < 29) {
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);
256         } else {
257             output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER | MagnesiumPathArgument.AID_COUNT_4B);
258             output.writeInt(size);
259         }
260
261         for (QName qname : qnames) {
262             writeQNameInternal(qname);
263         }
264     }
265
266     private void writeNodeIdentifier(final NodeIdentifier identifier) throws IOException {
267         writePathArgumentQName(identifier.getNodeType(), MagnesiumPathArgument.NODE_IDENTIFIER);
268     }
269
270     private void writeNodeIdentifierWithPredicates(final NodeIdentifierWithPredicates identifier) throws IOException {
271         final int size = identifier.size();
272         if (size < 5) {
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);
284         } else {
285             writePathArgumentQName(identifier.getNodeType(),
286                 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_4B));
287             output.writeInt(size);
288         }
289
290         writePredicates(identifier);
291     }
292
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());
297         }
298     }
299
300     private void writeNodeWithValue(final NodeWithValue<?> identifier) throws IOException {
301         writePathArgumentQName(identifier.getNodeType(), MagnesiumPathArgument.NODE_WITH_VALUE);
302         writeObject(identifier.getValue());
303     }
304
305     private void writePathArgumentQName(final QName qname, final byte typeHeader) throws IOException {
306         final Integer code = qnameCodeMap.get(qname);
307         if (code != null) {
308             final int val = code;
309             if (val < 256) {
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);
315             } else {
316                 output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_4B);
317                 output.writeInt(val);
318             }
319         } else {
320             // implied '| MagnesiumPathArgument.QNAME_DEF'
321             output.writeByte(typeHeader);
322             encodeQName(qname);
323         }
324     }
325
326     @Override
327     final void writeYangInstanceIdentifierInternal(final YangInstanceIdentifier identifier) throws IOException {
328         writeValue(identifier);
329     }
330
331     private void writeObject(final @NonNull Object value) throws IOException {
332         if (value instanceof String str) {
333             writeValue(str);
334         } else if (value instanceof Boolean bool) {
335             writeValue(bool);
336         } else if (value instanceof Byte byteVal) {
337             writeValue(byteVal);
338         } else if (value instanceof Short shortVal) {
339             writeValue(shortVal);
340         } else if (value instanceof Integer intVal) {
341             writeValue(intVal);
342         } else if (value instanceof Long longVal) {
343             writeValue(longVal);
344         } else if (value instanceof Uint8 uint8) {
345             writeValue(uint8);
346         } else if (value instanceof Uint16 uint16) {
347             writeValue(uint16);
348         } else if (value instanceof Uint32 uint32) {
349             writeValue(uint32);
350         } else if (value instanceof Uint64 uint64) {
351             writeValue(uint64);
352         } else if (value instanceof QName qname) {
353             writeQNameInternal(qname);
354         } else if (value instanceof YangInstanceIdentifier id) {
355             writeValue(id);
356         } else if (value instanceof byte[] bytes) {
357             writeValue(bytes);
358         } else if (value instanceof Empty) {
359             output.writeByte(MagnesiumValue.EMPTY);
360         } else if (value instanceof Set<?> set) {
361             writeValue(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) {
366             writeValue(val);
367         } else {
368             throw new IOException("Unhandled value type " + value.getClass());
369         }
370     }
371
372     private void writeValue(final boolean value) throws IOException {
373         output.writeByte(value ? MagnesiumValue.BOOLEAN_TRUE : MagnesiumValue.BOOLEAN_FALSE);
374     }
375
376     private void writeValue(final byte value) throws IOException {
377         if (value != 0) {
378             output.writeByte(MagnesiumValue.INT8);
379             output.writeByte(value);
380         } else {
381             output.writeByte(MagnesiumValue.INT8_0);
382         }
383     }
384
385     private void writeValue(final short value) throws IOException {
386         if (value != 0) {
387             output.writeByte(MagnesiumValue.INT16);
388             output.writeShort(value);
389         } else {
390             output.writeByte(MagnesiumValue.INT16_0);
391         }
392     }
393
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);
401         } else {
402             output.writeByte(MagnesiumValue.INT32_0);
403         }
404     }
405
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);
413         } else {
414             output.writeByte(MagnesiumValue.INT64_0);
415         }
416     }
417
418     private void writeValue(final Uint8 value) throws IOException {
419         final byte b = value.byteValue();
420         if (b != 0) {
421             output.writeByte(MagnesiumValue.UINT8);
422             output.writeByte(b);
423         } else {
424             output.writeByte(MagnesiumValue.UINT8_0);
425         }
426     }
427
428     private void writeValue(final Uint16 value) throws IOException {
429         final short s = value.shortValue();
430         if (s != 0) {
431             output.writeByte(MagnesiumValue.UINT16);
432             output.writeShort(s);
433         } else {
434             output.writeByte(MagnesiumValue.UINT16_0);
435         }
436     }
437
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);
442             output.writeInt(i);
443         } else if (i != 0) {
444             output.writeByte(MagnesiumValue.UINT32_2B);
445             output.writeShort(i);
446         } else {
447             output.writeByte(MagnesiumValue.UINT32_0);
448         }
449     }
450
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);
455             output.writeLong(l);
456         } else if (l != 0) {
457             output.writeByte(MagnesiumValue.UINT64_4B);
458             output.writeInt((int) l);
459         } else {
460             output.writeByte(MagnesiumValue.UINT64_0);
461         }
462     }
463
464     abstract void writeValue(BigInteger value) throws IOException;
465
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);
477             } else {
478                 output.writeByte(MagnesiumValue.STRING_4B);
479                 output.writeInt(bytes.length);
480             }
481             output.write(bytes);
482         } else {
483             output.writeByte(MagnesiumValue.STRING_CHARS);
484             output.writeInt(value.length());
485             output.writeChars(value);
486         }
487     }
488
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);
498         } else {
499             output.writeByte(MagnesiumValue.BINARY_4B);
500             output.writeInt(value.length);
501         }
502         output.write(value);
503     }
504
505     private void writeValue(final YangInstanceIdentifier value) throws IOException {
506         final List<PathArgument> args = value.getPathArguments();
507         final int size = args.size();
508         if (size > 31) {
509             output.writeByte(MagnesiumValue.YIID);
510             output.writeInt(size);
511         } else {
512             output.writeByte(MagnesiumValue.YIID_0 + size);
513         }
514         for (PathArgument arg : args) {
515             writePathArgumentInternal(arg);
516         }
517     }
518
519     private void writeValue(final Set<?> value) throws IOException {
520         final int size = value.size();
521         if (size < 29) {
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);
529         } else {
530             output.writeByte(MagnesiumValue.BITS_4B);
531             output.writeInt(size);
532         }
533
534         for (Object bit : value) {
535             checkArgument(bit instanceof String, "Expected value type to be String but was %s", bit);
536             encodeString((String) bit);
537         }
538     }
539
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());
545     }
546
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)) {
552             output.write(type);
553         } else {
554             writeQNameNode(type, qname);
555         }
556         stack.push(name);
557     }
558
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());
562         stack.push(name);
563     }
564
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);
569     }
570
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);
575         if (code == null) {
576             output.writeByte(type | MagnesiumNode.ADDR_DEFINE);
577             encodeQName(qname);
578         } else {
579             writeNodeType(type, code);
580         }
581     }
582
583     // Write a node type + lookup
584     private void writeNodeType(final int type, final int code) throws IOException {
585         if (code <= 255) {
586             output.writeByte(type | MagnesiumNode.ADDR_LOOKUP_1B);
587             output.writeByte(code);
588         } else {
589             output.writeByte(type | MagnesiumNode.ADDR_LOOKUP_4B);
590             output.writeInt(code);
591         }
592     }
593
594     // Encode a QName using lookup tables, resuling either in a reference to an existing entry, or emitting two
595     // String values.
596     private void encodeQName(final @NonNull QName qname) throws IOException {
597         final Integer prev = qnameCodeMap.put(qname, qnameCodeMap.size());
598         if (prev != null) {
599             throw new IOException("Internal coding error: attempted to re-encode " + qname + "%s already encoded as "
600                     + prev);
601         }
602
603         final QNameModule module = qname.getModule();
604         final Integer code = moduleCodeMap.get(module);
605         if (code == null) {
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());
611             } else {
612                 output.writeByte(MagnesiumValue.STRING_EMPTY);
613             }
614         } else {
615             writeModuleRef(code);
616         }
617         encodeString(qname.getLocalName());
618     }
619
620     // Encode a String using lookup tables, resulting either in a reference to an existing entry, or emitting as
621     // a literal value
622     private void encodeString(final @NonNull String str) throws IOException {
623         final Integer code = stringCodeMap.get(str);
624         if (code != null) {
625             writeRef(code);
626         } else {
627             stringCodeMap.put(str, stringCodeMap.size());
628             writeValue(str);
629         }
630     }
631
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;
636         if (val < 256) {
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);
642         } else {
643             output.writeByte(MagnesiumValue.QNAME_REF_4B);
644             output.writeInt(val);
645         }
646     }
647
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;
651         if (val < 256) {
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);
657         } else {
658             output.writeByte(MagnesiumValue.STRING_REF_4B);
659             output.writeInt(val);
660         }
661     }
662
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;
666         if (val < 256) {
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);
672         } else {
673             output.writeByte(MagnesiumValue.MODREF_4B);
674             output.writeInt(val);
675         }
676     }
677 }