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