1 /*
2  * %CopyrightBegin%
3  *
4  * Copyright Ericsson AB 2000-2017. All Rights Reserved.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * %CopyrightEnd%
19  */
20 package com.ericsson.otp.erlang;
21 
22 // import java.io.OutputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.IOException;
25 import java.io.OutputStream;
26 import java.io.UnsupportedEncodingException;
27 import java.math.BigDecimal;
28 import java.math.BigInteger;
29 import java.text.DecimalFormat;
30 import java.util.zip.Deflater;
31 
32 /**
33  * Provides a stream for encoding Erlang terms to external format, for
34  * transmission or storage.
35  *
36  * <p>
37  * Note that this class is not synchronized, if you need synchronization you
38  * must provide it yourself.
39  *
40  */
41 public class OtpOutputStream extends ByteArrayOutputStream {
42     /** The default initial size of the stream. * */
43     public static final int defaultInitialSize = 2048;
44 
45     /**
46      * The default increment used when growing the stream (increment at least
47      * this much). *
48      */
49     public static final int defaultIncrement = 2048;
50 
51     // static formats, used to encode floats and doubles
52     @SuppressWarnings("unused")
53     private static final DecimalFormat eform = new DecimalFormat("e+00;e-00");
54     @SuppressWarnings("unused")
55     private static final BigDecimal ten = new BigDecimal(10.0);
56     @SuppressWarnings("unused")
57     private static final BigDecimal one = new BigDecimal(1.0);
58 
59     private int fixedSize = Integer.MAX_VALUE;
60 
61     /**
62      * Create a stream with the default initial size (2048 bytes).
63      */
OtpOutputStream()64     public OtpOutputStream() {
65         this(defaultInitialSize);
66     }
67 
68     /**
69      * Create a stream with the specified initial size.
70      */
OtpOutputStream(final int size)71     public OtpOutputStream(final int size) {
72         super(size);
73     }
74 
75     /**
76      * Create a stream containing the encoded version of the given Erlang term.
77      */
OtpOutputStream(final OtpErlangObject o)78     public OtpOutputStream(final OtpErlangObject o) {
79         this();
80         write_any(o);
81     }
82 
83     // package scope
84     /*
85      * Get the contents of the output stream as an input stream instead. This is
86      * used internally in {@link OtpCconnection} for tracing outgoing packages.
87      *
88      * @param offset where in the output stream to read data from when creating
89      * the input stream. The offset is necessary because header contents start 5
90      * bytes into the header buffer, whereas payload contents start at the
91      * beginning
92      *
93      * @return an input stream containing the same raw data.
94      */
getOtpInputStream(final int offset)95     OtpInputStream getOtpInputStream(final int offset) {
96         return new OtpInputStream(super.buf, offset, super.count - offset, 0);
97     }
98 
99     /**
100      * Get the current position in the stream.
101      *
102      * @return the current position in the stream.
103      */
getPos()104     public int getPos() {
105         return super.count;
106     }
107 
108     /**
109      * Trims the capacity of this <tt>OtpOutputStream</tt> instance to be the
110      * buffer's current size. An application can use this operation to minimize
111      * the storage of an <tt>OtpOutputStream</tt> instance.
112      */
trimToSize()113     public void trimToSize() {
114         resize(super.count);
115     }
116 
resize(final int size)117     private void resize(final int size) {
118         if (size < super.buf.length) {
119             final byte[] tmp = new byte[size];
120             System.arraycopy(super.buf, 0, tmp, 0, size);
121             super.buf = tmp;
122         } else if (size > super.buf.length) {
123             ensureCapacity(size);
124         }
125     }
126 
127     /**
128      * Increases the capacity of this <tt>OtpOutputStream</tt> instance, if
129      * necessary, to ensure that it can hold at least the number of elements
130      * specified by the minimum capacity argument.
131      *
132      * @param minCapacity
133      *            the desired minimum capacity
134      */
ensureCapacity(final int minCapacity)135     public void ensureCapacity(final int minCapacity) {
136         if (minCapacity > fixedSize) {
137             throw new IllegalArgumentException(
138                     "Trying to increase fixed-size buffer");
139         }
140         final int oldCapacity = super.buf.length;
141         if (minCapacity > oldCapacity) {
142             int newCapacity = oldCapacity * 3 / 2 + 1;
143             if (newCapacity < oldCapacity + defaultIncrement) {
144                 newCapacity = oldCapacity + defaultIncrement;
145             }
146             if (newCapacity < minCapacity) {
147                 newCapacity = minCapacity;
148             }
149             newCapacity = Math.min(fixedSize, newCapacity);
150             // minCapacity is usually close to size, so this is a win:
151             final byte[] tmp = new byte[newCapacity];
152             System.arraycopy(super.buf, 0, tmp, 0, super.count);
153             super.buf = tmp;
154         }
155     }
156 
157     /**
158      * Write one byte to the stream.
159      *
160      * @param b
161      *            the byte to write.
162      *
163      */
write(final byte b)164     public void write(final byte b) {
165         ensureCapacity(super.count + 1);
166         super.buf[super.count++] = b;
167     }
168 
169     /*
170      * (non-Javadoc)
171      *
172      * @see java.io.ByteArrayOutputStream#write(byte[])
173      */
174     @Override
write(final byte[] abuf)175     public void write(final byte[] abuf) {
176         // don't assume that super.write(byte[]) calls write(buf, 0, buf.length)
177         write(abuf, 0, abuf.length);
178     }
179 
180     /*
181      * (non-Javadoc)
182      *
183      * @see java.io.ByteArrayOutputStream#write(int)
184      */
185     @Override
write(final int b)186     public synchronized void write(final int b) {
187         ensureCapacity(super.count + 1);
188         super.buf[super.count] = (byte) b;
189         count += 1;
190     }
191 
192     /*
193      * (non-Javadoc)
194      *
195      * @see java.io.ByteArrayOutputStream#write(byte[], int, int)
196      */
197     @Override
write(final byte[] b, final int off, final int len)198     public synchronized void write(final byte[] b, final int off, final int len) {
199         if (off < 0 || off > b.length || len < 0 || off + len - b.length > 0) {
200             throw new IndexOutOfBoundsException();
201         }
202         ensureCapacity(super.count + len);
203         System.arraycopy(b, off, super.buf, super.count, len);
204         super.count += len;
205     }
206 
207     @Override
writeTo(OutputStream out)208     public synchronized void writeTo(OutputStream out) throws IOException {
209         super.writeTo(out);
210     }
211 
writeToAndFlush(OutputStream out)212     public synchronized void writeToAndFlush(OutputStream out) throws IOException {
213         super.writeTo(out);
214         out.flush();
215     }
216 
217     /**
218      * Write the low byte of a value to the stream.
219      *
220      * @param n
221      *            the value to use.
222      *
223      */
write1(final long n)224     public void write1(final long n) {
225         write((byte) (n & 0xff));
226     }
227 
228     /**
229      * Write an array of bytes to the stream.
230      *
231      * @param bytes
232      *            the array of bytes to write.
233      *
234      */
writeN(final byte[] bytes)235     public void writeN(final byte[] bytes) {
236         write(bytes);
237     }
238 
239     /**
240      * Get the current capacity of the stream. As bytes are added the capacity
241      * of the stream is increased automatically, however this method returns the
242      * current size.
243      *
244      * @return the size of the internal buffer used by the stream.
245      */
length()246     public int length() {
247         return super.buf.length;
248     }
249 
250     /**
251      * Get the number of bytes in the stream.
252      *
253      * @return the number of bytes in the stream.
254      *
255      * @deprecated As of Jinterface 1.4, replaced by super.size().
256      * @see #size()
257      */
258 
259     @Deprecated
count()260     public int count() {
261         return count;
262     }
263 
264     /**
265      * Write the low two bytes of a value to the stream in big endian order.
266      *
267      * @param n
268      *            the value to use.
269      */
write2BE(final long n)270     public void write2BE(final long n) {
271         write((byte) ((n & 0xff00) >> 8));
272         write((byte) (n & 0xff));
273     }
274 
275     /**
276      * Write the low four bytes of a value to the stream in big endian order.
277      *
278      * @param n
279      *            the value to use.
280      */
write4BE(final long n)281     public void write4BE(final long n) {
282         write((byte) ((n & 0xff000000) >> 24));
283         write((byte) ((n & 0xff0000) >> 16));
284         write((byte) ((n & 0xff00) >> 8));
285         write((byte) (n & 0xff));
286     }
287 
288     /**
289      * Write the low eight (all) bytes of a value to the stream in big endian
290      * order.
291      *
292      * @param n
293      *            the value to use.
294      */
write8BE(final long n)295     public void write8BE(final long n) {
296         write((byte) (n >> 56 & 0xff));
297         write((byte) (n >> 48 & 0xff));
298         write((byte) (n >> 40 & 0xff));
299         write((byte) (n >> 32 & 0xff));
300         write((byte) (n >> 24 & 0xff));
301         write((byte) (n >> 16 & 0xff));
302         write((byte) (n >> 8 & 0xff));
303         write((byte) (n & 0xff));
304     }
305 
306     /**
307      * Write any number of bytes in little endian format.
308      *
309      * @param n
310      *            the value to use.
311      * @param b
312      *            the number of bytes to write from the little end.
313      */
writeLE(final long n, final int b)314     public void writeLE(final long n, final int b) {
315         long v = n;
316         for (int i = 0; i < b; i++) {
317             write((byte) (v & 0xff));
318             v >>= 8;
319         }
320     }
321 
322     /**
323      * Write the low two bytes of a value to the stream in little endian order.
324      *
325      * @param n
326      *            the value to use.
327      */
write2LE(final long n)328     public void write2LE(final long n) {
329         write((byte) (n & 0xff));
330         write((byte) ((n & 0xff00) >> 8));
331     }
332 
333     /**
334      * Write the low four bytes of a value to the stream in little endian order.
335      *
336      * @param n
337      *            the value to use.
338      */
write4LE(final long n)339     public void write4LE(final long n) {
340         write((byte) (n & 0xff));
341         write((byte) ((n & 0xff00) >> 8));
342         write((byte) ((n & 0xff0000) >> 16));
343         write((byte) ((n & 0xff000000) >> 24));
344     }
345 
346     /**
347      * Write the low eight bytes of a value to the stream in little endian
348      * order.
349      *
350      * @param n
351      *            the value to use.
352      */
write8LE(final long n)353     public void write8LE(final long n) {
354         write((byte) (n & 0xff));
355         write((byte) (n >> 8 & 0xff));
356         write((byte) (n >> 16 & 0xff));
357         write((byte) (n >> 24 & 0xff));
358         write((byte) (n >> 32 & 0xff));
359         write((byte) (n >> 40 & 0xff));
360         write((byte) (n >> 48 & 0xff));
361         write((byte) (n >> 56 & 0xff));
362     }
363 
364     /**
365      * Write the low four bytes of a value to the stream in bif endian order, at
366      * the specified position. If the position specified is beyond the end of
367      * the stream, this method will have no effect.
368      *
369      * Normally this method should be used in conjunction with {@link #size()
370      * size()}, when is is necessary to insert data into the stream before it is
371      * known what the actual value should be. For example:
372      *
373      * <pre>
374      * int pos = s.size();
375      *    s.write4BE(0); // make space for length data,
376      *                   // but final value is not yet known
377      *     [ ...more write statements...]
378      *    // later... when we know the length value
379      *    s.poke4BE(pos, length);
380      * </pre>
381      *
382      *
383      * @param offset
384      *            the position in the stream.
385      * @param n
386      *            the value to use.
387      */
poke4BE(final int offset, final long n)388     public void poke4BE(final int offset, final long n) {
389         if (offset < super.count) {
390             buf[offset + 0] = (byte) ((n & 0xff000000) >> 24);
391             buf[offset + 1] = (byte) ((n & 0xff0000) >> 16);
392             buf[offset + 2] = (byte) ((n & 0xff00) >> 8);
393             buf[offset + 3] = (byte) (n & 0xff);
394         }
395     }
396 
397     /**
398      * Write a string to the stream as an Erlang atom.
399      *
400      * @param atom
401      *            the string to write.
402      */
write_atom(final String atom)403     public void write_atom(final String atom) {
404         String enc_atom;
405         byte[] bytes;
406 
407         if (atom.codePointCount(0, atom.length()) <= OtpExternal.maxAtomLength) {
408             enc_atom = atom;
409         } else {
410             /*
411              * Throwing an exception would be better I think, but truncation
412              * seems to be the way it has been done in other parts of OTP...
413              */
414             enc_atom = new String(OtpErlangString.stringToCodePoints(atom), 0,
415                     OtpExternal.maxAtomLength);
416         }
417 
418         try {
419             bytes = enc_atom.getBytes("UTF-8");
420             final int length = bytes.length;
421             if (length < 256) {
422                 write1(OtpExternal.smallAtomUtf8Tag);
423                 write1(length);
424             } else {
425                 write1(OtpExternal.atomUtf8Tag);
426                 write2BE(length);
427             }
428             writeN(bytes);
429         } catch (final java.io.UnsupportedEncodingException e) {
430             /*
431              * Sigh, why didn't the API designer add an OtpErlangEncodeException
432              * to these encoding functions?!? Instead of changing the API we
433              * write an invalid atom and let it fail for whoever trying to
434              * decode this... Sigh, again...
435              */
436             write1(OtpExternal.smallAtomUtf8Tag);
437             write1(2);
438             write2BE(0xffff); /* Invalid UTF-8 */
439         }
440     }
441 
442     /**
443      * Write an array of bytes to the stream as an Erlang binary.
444      *
445      * @param bin
446      *            the array of bytes to write.
447      */
write_binary(final byte[] bin)448     public void write_binary(final byte[] bin) {
449         write1(OtpExternal.binTag);
450         write4BE(bin.length);
451         writeN(bin);
452     }
453 
454     /**
455      * Write an array of bytes to the stream as an Erlang bitstr.
456      *
457      * @param bin
458      *            the array of bytes to write.
459      * @param pad_bits
460      *            the number of zero pad bits at the low end of the last byte
461      */
write_bitstr(final byte[] bin, final int pad_bits)462     public void write_bitstr(final byte[] bin, final int pad_bits) {
463         if (pad_bits == 0) {
464             write_binary(bin);
465             return;
466         }
467         write1(OtpExternal.bitBinTag);
468         write4BE(bin.length);
469         write1(8 - pad_bits);
470         writeN(bin);
471     }
472 
473     /**
474      * Write a boolean value to the stream as the Erlang atom 'true' or 'false'.
475      *
476      * @param b
477      *            the boolean value to write.
478      */
write_boolean(final boolean b)479     public void write_boolean(final boolean b) {
480         write_atom(String.valueOf(b));
481     }
482 
483     /**
484      * Write a single byte to the stream as an Erlang integer. The byte is
485      * really an IDL 'octet', that is, unsigned.
486      *
487      * @param b
488      *            the byte to use.
489      */
write_byte(final byte b)490     public void write_byte(final byte b) {
491         this.write_long(b & 0xffL, true);
492     }
493 
494     /**
495      * Write a character to the stream as an Erlang integer. The character may
496      * be a 16 bit character, kind of IDL 'wchar'. It is up to the Erlang side
497      * to take care of souch, if they should be used.
498      *
499      * @param c
500      *            the character to use.
501      */
write_char(final char c)502     public void write_char(final char c) {
503         this.write_long(c & 0xffffL, true);
504     }
505 
506     /**
507      * Write a double value to the stream.
508      *
509      * @param d
510      *            the double to use.
511      */
write_double(final double d)512     public void write_double(final double d) {
513         write1(OtpExternal.newFloatTag);
514         write8BE(Double.doubleToLongBits(d));
515     }
516 
517     /**
518      * Write a float value to the stream.
519      *
520      * @param f
521      *            the float to use.
522      */
write_float(final float f)523     public void write_float(final float f) {
524         write_double(f);
525     }
526 
write_big_integer(final BigInteger v)527     public void write_big_integer(final BigInteger v) {
528         if (v.bitLength() < 64) {
529             this.write_long(v.longValue(), true);
530             return;
531         }
532         final int signum = v.signum();
533         BigInteger val = v;
534         if (signum < 0) {
535             val = val.negate();
536         }
537         final byte[] magnitude = val.toByteArray();
538         final int n = magnitude.length;
539         // Reverse the array to make it little endian.
540         for (int i = 0, j = n; i < j--; i++) {
541             // Swap [i] with [j]
542             final byte b = magnitude[i];
543             magnitude[i] = magnitude[j];
544             magnitude[j] = b;
545         }
546         if ((n & 0xFF) == n) {
547             write1(OtpExternal.smallBigTag);
548             write1(n); // length
549         } else {
550             write1(OtpExternal.largeBigTag);
551             write4BE(n); // length
552         }
553         write1(signum < 0 ? 1 : 0); // sign
554         // Write the array
555         writeN(magnitude);
556     }
557 
558     void write_long(final long v, final boolean unsigned) {
559         /*
560          * If v<0 and unsigned==true the value
561          * java.lang.Long.MAX_VALUE-java.lang.Long.MIN_VALUE+1+v is written, i.e
562          * v is regarded as unsigned two's complement.
563          */
564         if ((v & 0xffL) == v) {
565             // will fit in one byte
566             write1(OtpExternal.smallIntTag);
567             write1(v);
568         } else {
569             // note that v != 0L
570             if (v < 0 && unsigned || v < OtpExternal.erlMin
571                     || v > OtpExternal.erlMax) {
572                 // some kind of bignum
573                 final long abs = unsigned ? v : v < 0 ? -v : v;
574                 final int sign = unsigned ? 0 : v < 0 ? 1 : 0;
575                 int n;
576                 long mask;
577                 for (mask = 0xFFFFffffL, n = 4; (abs & mask) != abs; n++, mask = mask << 8 | 0xffL) {
578                     // count nonzero bytes
579                 }
580                 write1(OtpExternal.smallBigTag);
581                 write1(n); // length
582                 write1(sign); // sign
583                 writeLE(abs, n); // value. obs! little endian
584             } else {
585                 write1(OtpExternal.intTag);
586                 write4BE(v);
587             }
588         }
589     }
590 
591     /**
592      * Write a long to the stream.
593      *
594      * @param l
595      *            the long to use.
596      */
597     public void write_long(final long l) {
598         this.write_long(l, false);
599     }
600 
601     /**
602      * Write a positive long to the stream. The long is interpreted as a two's
603      * complement unsigned long even if it is negative.
604      *
605      * @param ul
606      *            the long to use.
607      */
608     public void write_ulong(final long ul) {
609         this.write_long(ul, true);
610     }
611 
612     /**
613      * Write an integer to the stream.
614      *
615      * @param i
616      *            the integer to use.
617      */
618     public void write_int(final int i) {
619         this.write_long(i, false);
620     }
621 
622     /**
623      * Write a positive integer to the stream. The integer is interpreted as a
624      * two's complement unsigned integer even if it is negative.
625      *
626      * @param ui
627      *            the integer to use.
628      */
629     public void write_uint(final int ui) {
630         this.write_long(ui & 0xFFFFffffL, true);
631     }
632 
633     /**
634      * Write a short to the stream.
635      *
636      * @param s
637      *            the short to use.
638      */
639     public void write_short(final short s) {
640         this.write_long(s, false);
641     }
642 
643     /**
644      * Write a positive short to the stream. The short is interpreted as a two's
645      * complement unsigned short even if it is negative.
646      *
647      * @param us
648      *            the short to use.
649      */
650     public void write_ushort(final short us) {
651         this.write_long(us & 0xffffL, true);
652     }
653 
654     /**
655      * Write an Erlang list header to the stream. After calling this method, you
656      * must write 'arity' elements to the stream followed by nil, or it will not
657      * be possible to decode it later.
658      *
659      * @param arity
660      *            the number of elements in the list.
661      */
662     public void write_list_head(final int arity) {
663         if (arity == 0) {
664             write_nil();
665         } else {
666             write1(OtpExternal.listTag);
667             write4BE(arity);
668         }
669     }
670 
671     /**
672      * Write an empty Erlang list to the stream.
673      */
674     public void write_nil() {
675         write1(OtpExternal.nilTag);
676     }
677 
678     /**
679      * Write an Erlang tuple header to the stream. After calling this method,
680      * you must write 'arity' elements to the stream or it will not be possible
681      * to decode it later.
682      *
683      * @param arity
684      *            the number of elements in the tuple.
685      */
686     public void write_tuple_head(final int arity) {
687         if (arity < 0xff) {
688             write1(OtpExternal.smallTupleTag);
689             write1(arity);
690         } else {
691             write1(OtpExternal.largeTupleTag);
692             write4BE(arity);
693         }
694     }
695 
696     /**
697      * Write an Erlang PID to the stream.
698      *
699      * @param node
700      *            the nodename.
701      *
702      * @param id
703      *            an arbitrary number. Only the low order 15 bits will be used.
704      *
705      * @param serial
706      *            another arbitrary number. Only the low order 13 bits will be
707      *            used.
708      *
709      * @param creation
710      *            yet another arbitrary number. Only the low order 2 bits will
711      *            be used.
712      *
713      */
714     public void write_pid(final String node, final int id, final int serial,
715             final int creation) {
716 	write1(OtpExternal.pidTag);
717 	write_atom(node);
718 	write4BE(id & 0x7fff); // 15 bits
719 	write4BE(serial & 0x1fff); // 13 bits
720 	write1(creation & 0x3); // 2 bits
721     }
722 
723     /**
724      * Write an Erlang PID to the stream.
725      *
726      * @param pid
727      *            the pid
728      */
729     public void write_pid(OtpErlangPid pid) {
730 	write1(pid.tag());
731 	write_atom(pid.node());
732 	write4BE(pid.id());
733 	write4BE(pid.serial());
734 	switch (pid.tag()) {
735 	case OtpExternal.pidTag:
736 	    write1(pid.creation());
737 	    break;
738 	case OtpExternal.newPidTag:
739 	    write4BE(pid.creation());
740 	    break;
741 	default:
742 	    throw new AssertionError("Invalid pid tag " + pid.tag());
743 	}
744     }
745 
746 
747     /**
748      * Write an Erlang port to the stream.
749      *
750      * @param node
751      *            the nodename.
752      *
753      * @param id
754      *            an arbitrary number. Only the low order 28 bits will be used.
755      *
756      * @param creation
757      *            another arbitrary number. Only the low order 2 bits will
758      *            be used.
759      */
760     public void write_port(final String node, final int id, final int creation) {
761 	write1(OtpExternal.portTag);
762 	write_atom(node);
763 	write4BE(id & 0xfffffff); // 28 bits
764 	write1(creation & 0x3); // 2 bits
765     }
766 
767     /**
768      * Write an Erlang port to the stream.
769      *
770      * @param port
771      *            the port.
772      */
773     public void write_port(OtpErlangPort port) {
774 	write1(port.tag());
775 	write_atom(port.node());
776 	write4BE(port.id());
777 	switch (port.tag()) {
778 	case OtpExternal.portTag:
779 	    write1(port.creation());
780 	    break;
781 	case OtpExternal.newPortTag:
782 	    write4BE(port.creation());
783 	    break;
784 	default:
785 	    throw new AssertionError("Invalid port tag " + port.tag());
786 	}
787     }
788 
789     /**
790      * Write an old style Erlang ref to the stream.
791      *
792      * @param node
793      *            the nodename.
794      *
795      * @param id
796      *            an arbitrary number. Only the low order 18 bits will be used.
797      *
798      * @param creation
799      *            another arbitrary number.
800      *
801      */
802     public void write_ref(final String node, final int id, final int creation) {
803 	/* Always encode as an extended reference; all
804 	   participating parties are now expected to be
805 	   able to decode extended references. */
806 	int ids[] = new int[1];
807 	ids[0] = id;
808 	write_ref(node, ids, creation);
809     }
810 
811     /**
812      * Write an Erlang ref to the stream.
813      *
814      * @param node
815      *            the nodename.
816      *
817      * @param ids
818      *            an array of arbitrary numbers. Only the low order 18 bits of
819      *            the first number will be used. At most three numbers
820      *            will be read from the array.
821      *
822      * @param creation
823      *            another arbitrary number. Only the low order 2 bits will be used.
824      *
825      */
826     public void write_ref(final String node, final int[] ids, final int creation) {
827         int arity = ids.length;
828         if (arity > 3) {
829             arity = 3; // max 3 words in ref
830         }
831 
832 	write1(OtpExternal.newRefTag);
833 
834 	// how many id values
835 	write2BE(arity);
836 
837 	write_atom(node);
838 
839 	write1(creation & 0x3); // 2 bits
840 
841 	// first int gets truncated to 18 bits
842 	write4BE(ids[0] & 0x3ffff);
843 
844 	// remaining ones are left as is
845 	for (int i = 1; i < arity; i++) {
846 	    write4BE(ids[i]);
847 	}
848     }
849 
850     /**
851      * Write an Erlang ref to the stream.
852      *
853      * @param ref
854      *            the reference
855      */
856     public void write_ref(OtpErlangRef ref) {
857 	int[] ids = ref.ids();
858         int arity = ids.length;
859 
860 	write1(ref.tag());
861 	write2BE(arity);
862 	write_atom(ref.node());
863 
864 	switch (ref.tag()) {
865 	case OtpExternal.newRefTag:
866 	    write1(ref.creation());
867 	    write4BE(ids[0] & 0x3ffff); // first word gets truncated to 18 bits
868 	    break;
869 	case OtpExternal.newerRefTag:
870 	    write4BE(ref.creation());
871 	    write4BE(ids[0]); // full first word
872 	    break;
873 	default:
874 	    throw new AssertionError("Invalid ref tag " + ref.tag());
875 	}
876 
877 	for (int i = 1; i < arity; i++) {
878 	    write4BE(ids[i]);
879 	}
880     }
881 
882     /**
883      * Write a string to the stream.
884      *
885      * @param s
886      *            the string to write.
887      */
888     public void write_string(final String s) {
889         final int len = s.length();
890 
891         switch (len) {
892         case 0:
893             write_nil();
894             break;
895         default:
896             if (len <= 65535 && is8bitString(s)) { // 8-bit string
897                 try {
898                     final byte[] bytebuf = s.getBytes("ISO-8859-1");
899                     write1(OtpExternal.stringTag);
900                     write2BE(len);
901                     writeN(bytebuf);
902                 } catch (final UnsupportedEncodingException e) {
903                     write_nil(); // it should never ever get here...
904                 }
905             } else { // unicode or longer, must code as list
906                 final int[] codePoints = OtpErlangString.stringToCodePoints(s);
907                 write_list_head(codePoints.length);
908                 for (final int codePoint : codePoints) {
909                     write_int(codePoint);
910                 }
911                 write_nil();
912             }
913         }
914     }
915 
916     private boolean is8bitString(final String s) {
917         for (int i = 0; i < s.length(); ++i) {
918             final char c = s.charAt(i);
919             if (c < 0 || c > 255) {
920                 return false;
921             }
922         }
923         return true;
924     }
925 
926     /**
927      * Write an arbitrary Erlang term to the stream in compressed format.
928      *
929      * @param o
930      *            the Erlang term to write.
931      */
932     public void write_compressed(final OtpErlangObject o) {
933         write_compressed(o, Deflater.DEFAULT_COMPRESSION);
934     }
935 
936     /**
937      * Write an arbitrary Erlang term to the stream in compressed format.
938      *
939      * @param o
940      *            the Erlang term to write.
941      * @param level
942      *            the compression level (<tt>0..9</tt>)
943      */
944     public void write_compressed(final OtpErlangObject o, final int level) {
945         @SuppressWarnings("resource")
946         final OtpOutputStream oos = new OtpOutputStream(o);
947         /*
948          * similar to erts_term_to_binary() in external.c: We don't want to
949          * compress if compression actually increases the size. Since
950          * compression uses 5 extra bytes (COMPRESSED tag + size), don't
951          * compress if the original term is smaller.
952          */
953         if (oos.size() < 5) {
954             // fast path for small terms
955             try {
956                 oos.writeToAndFlush(this);
957                 // if the term is written as a compressed term, the output
958                 // stream is closed, so we do this here, too
959                 close();
960             } catch (final IOException e) {
961                 throw new java.lang.IllegalArgumentException(
962                         "Intermediate stream failed for Erlang object " + o);
963             }
964         } else {
965             final int startCount = super.count;
966             // we need destCount bytes for an uncompressed term
967             // -> if compression uses more, use the uncompressed term!
968             final int destCount = startCount + oos.size();
969             fixedSize = destCount;
970             final Deflater def = new Deflater(level);
971             final java.util.zip.DeflaterOutputStream dos = new java.util.zip.DeflaterOutputStream(
972                     this, def);
973             try {
974                 write1(OtpExternal.compressedTag);
975                 write4BE(oos.size());
976                 oos.writeTo(dos);
977                 dos.close(); // note: closes this, too!
978             } catch (final IllegalArgumentException e) {
979                 /*
980                  * Discard further un-compressed data (if not called, there may
981                  * be memory leaks).
982                  *
983                  * After calling java.util.zip.Deflater.end(), the deflater
984                  * should not be used anymore, not even the close() method of
985                  * dos. Calling dos.close() before def.end() is prevented since
986                  * an unfinished DeflaterOutputStream will try to deflate its
987                  * unprocessed data to the (fixed) byte array which is prevented
988                  * by ensureCapacity() and would also unnecessarily process
989                  * further data that is discarded anyway.
990                  *
991                  * Since we are re-using the byte array of this object below, we
992                  * must not call close() in e.g. a finally block either (with or
993                  * without a call to def.end()).
994                  */
995                 def.end();
996                 // could not make the value smaller than originally
997                 // -> reset to starting count, write uncompressed
998                 super.count = startCount;
999                 try {
1000                     oos.writeTo(this);
1001                     // if the term is written as a compressed term, the output
1002                     // stream is closed, so we do this here, too
1003                     close();
1004                 } catch (final IOException e2) {
1005                     throw new java.lang.IllegalArgumentException(
1006                             "Intermediate stream failed for Erlang object " + o);
1007                 }
1008             } catch (final IOException e) {
1009                 throw new java.lang.IllegalArgumentException(
1010                         "Intermediate stream failed for Erlang object " + o);
1011             } finally {
1012                 fixedSize = Integer.MAX_VALUE;
1013             }
1014         }
1015     }
1016 
1017     /**
1018      * Write an arbitrary Erlang term to the stream.
1019      *
1020      * @param o
1021      *            the Erlang term to write.
1022      */
1023     public void write_any(final OtpErlangObject o) {
1024         // calls one of the above functions, depending on o
1025         o.encode(this);
1026     }
1027 
1028     public void write_fun(final OtpErlangPid pid, final String module,
1029             final long old_index, final int arity, final byte[] md5,
1030             final long index, final long uniq, final OtpErlangObject[] freeVars) {
1031         if (arity == -1) {
1032             write1(OtpExternal.funTag);
1033             write4BE(freeVars.length);
1034             pid.encode(this);
1035             write_atom(module);
1036             write_long(index);
1037             write_long(uniq);
1038             for (final OtpErlangObject fv : freeVars) {
1039                 fv.encode(this);
1040             }
1041         } else {
1042             write1(OtpExternal.newFunTag);
1043             final int saveSizePos = getPos();
1044             write4BE(0); // this is where we patch in the size
1045             write1(arity);
1046             writeN(md5);
1047             write4BE(index);
1048             write4BE(freeVars.length);
1049             write_atom(module);
1050             write_long(old_index);
1051             write_long(uniq);
1052             pid.encode(this);
1053             for (final OtpErlangObject fv : freeVars) {
1054                 fv.encode(this);
1055             }
1056             poke4BE(saveSizePos, getPos() - saveSizePos);
1057         }
1058     }
1059 
1060     public void write_external_fun(final String module, final String function,
1061             final int arity) {
1062         write1(OtpExternal.externalFunTag);
1063         write_atom(module);
1064         write_atom(function);
1065         write_long(arity);
1066     }
1067 
1068     public void write_map_head(final int arity) {
1069         write1(OtpExternal.mapTag);
1070         write4BE(arity);
1071     }
1072 }
1073