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 <code>OtpOutputStream</code> instance to be the 110 * buffer's current size. An application can use this operation to minimize 111 * the storage of an <code>OtpOutputStream</code> 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 <code>OtpOutputStream</code> 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.newPidTag); 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(OtpExternal.newPidTag); 731 write_atom(pid.node()); 732 write4BE(pid.id()); 733 write4BE(pid.serial()); 734 write4BE(pid.creation()); 735 } 736 737 738 /** 739 * Write an Erlang port to the stream. 740 * 741 * @param node 742 * the nodename. 743 * 744 * @param id 745 * an arbitrary number. Only the low order 28 bits will be used. 746 * 747 * @param creation 748 * another arbitrary number. Only the low order 2 bits will 749 * be used. 750 */ 751 public void write_port(final String node, final int id, final int creation) { 752 write1(OtpExternal.newPortTag); 753 write_atom(node); 754 write4BE(id & 0xfffffff); // 28 bits 755 write1(creation & 0x3); // 2 bits 756 } 757 758 /** 759 * Write an Erlang port to the stream. 760 * 761 * @param port 762 * the port. 763 */ 764 public void write_port(OtpErlangPort port) { 765 write1(OtpExternal.newPortTag); 766 write_atom(port.node()); 767 write4BE(port.id()); 768 write4BE(port.creation()); 769 } 770 771 /** 772 * Write an old style Erlang ref to the stream. 773 * 774 * @param node 775 * the nodename. 776 * 777 * @param id 778 * an arbitrary number. Only the low order 18 bits will be used. 779 * 780 * @param creation 781 * another arbitrary number. 782 * 783 */ 784 public void write_ref(final String node, final int id, final int creation) { 785 /* Always encode as an extended reference; all 786 participating parties are now expected to be 787 able to decode extended references. */ 788 int ids[] = new int[1]; 789 ids[0] = id; 790 write_ref(node, ids, creation); 791 } 792 793 /** 794 * Write an Erlang ref to the stream. 795 * 796 * @param node 797 * the nodename. 798 * 799 * @param ids 800 * an array of arbitrary numbers. Only the low order 18 bits of 801 * the first number will be used. At most three numbers 802 * will be read from the array. 803 * 804 * @param creation 805 * another arbitrary number. Only the low order 2 bits will be used. 806 * 807 */ 808 public void write_ref(final String node, final int[] ids, final int creation) { 809 int arity = ids.length; 810 if (arity > 3) { 811 arity = 3; // max 3 words in ref 812 } 813 814 write1(OtpExternal.newerRefTag); 815 816 // how many id values 817 write2BE(arity); 818 819 write_atom(node); 820 821 write1(creation & 0x3); // 2 bits 822 823 // first int gets truncated to 18 bits 824 write4BE(ids[0] & 0x3ffff); 825 826 // remaining ones are left as is 827 for (int i = 1; i < arity; i++) { 828 write4BE(ids[i]); 829 } 830 } 831 832 /** 833 * Write an Erlang ref to the stream. 834 * 835 * @param ref 836 * the reference 837 */ 838 public void write_ref(OtpErlangRef ref) { 839 int[] ids = ref.ids(); 840 int arity = ids.length; 841 842 write1(OtpExternal.newerRefTag); 843 write2BE(arity); 844 write_atom(ref.node()); 845 write4BE(ref.creation()); 846 847 for (int i = 0; i < arity; i++) { 848 write4BE(ids[i]); 849 } 850 } 851 852 /** 853 * Write a string to the stream. 854 * 855 * @param s 856 * the string to write. 857 */ 858 public void write_string(final String s) { 859 final int len = s.length(); 860 861 switch (len) { 862 case 0: 863 write_nil(); 864 break; 865 default: 866 if (len <= 65535 && is8bitString(s)) { // 8-bit string 867 try { 868 final byte[] bytebuf = s.getBytes("ISO-8859-1"); 869 write1(OtpExternal.stringTag); 870 write2BE(len); 871 writeN(bytebuf); 872 } catch (final UnsupportedEncodingException e) { 873 write_nil(); // it should never ever get here... 874 } 875 } else { // unicode or longer, must code as list 876 final int[] codePoints = OtpErlangString.stringToCodePoints(s); 877 write_list_head(codePoints.length); 878 for (final int codePoint : codePoints) { 879 write_int(codePoint); 880 } 881 write_nil(); 882 } 883 } 884 } 885 886 private boolean is8bitString(final String s) { 887 for (int i = 0; i < s.length(); ++i) { 888 final char c = s.charAt(i); 889 if (c < 0 || c > 255) { 890 return false; 891 } 892 } 893 return true; 894 } 895 896 /** 897 * Write an arbitrary Erlang term to the stream in compressed format. 898 * 899 * @param o 900 * the Erlang term to write. 901 */ 902 public void write_compressed(final OtpErlangObject o) { 903 write_compressed(o, Deflater.DEFAULT_COMPRESSION); 904 } 905 906 /** 907 * Write an arbitrary Erlang term to the stream in compressed format. 908 * 909 * @param o 910 * the Erlang term to write. 911 * @param level 912 * the compression level (<code>0..9</code>) 913 */ 914 public void write_compressed(final OtpErlangObject o, final int level) { 915 @SuppressWarnings("resource") 916 final OtpOutputStream oos = new OtpOutputStream(o); 917 /* 918 * similar to erts_term_to_binary() in external.c: We don't want to 919 * compress if compression actually increases the size. Since 920 * compression uses 5 extra bytes (COMPRESSED tag + size), don't 921 * compress if the original term is smaller. 922 */ 923 if (oos.size() < 5) { 924 // fast path for small terms 925 try { 926 oos.writeToAndFlush(this); 927 // if the term is written as a compressed term, the output 928 // stream is closed, so we do this here, too 929 close(); 930 } catch (final IOException e) { 931 throw new java.lang.IllegalArgumentException( 932 "Intermediate stream failed for Erlang object " + o); 933 } 934 } else { 935 final int startCount = super.count; 936 // we need destCount bytes for an uncompressed term 937 // -> if compression uses more, use the uncompressed term! 938 final int destCount = startCount + oos.size(); 939 fixedSize = destCount; 940 final Deflater def = new Deflater(level); 941 final java.util.zip.DeflaterOutputStream dos = new java.util.zip.DeflaterOutputStream( 942 this, def); 943 try { 944 write1(OtpExternal.compressedTag); 945 write4BE(oos.size()); 946 oos.writeTo(dos); 947 dos.close(); // note: closes this, too! 948 } catch (final IllegalArgumentException e) { 949 /* 950 * Discard further un-compressed data (if not called, there may 951 * be memory leaks). 952 * 953 * After calling java.util.zip.Deflater.end(), the deflater 954 * should not be used anymore, not even the close() method of 955 * dos. Calling dos.close() before def.end() is prevented since 956 * an unfinished DeflaterOutputStream will try to deflate its 957 * unprocessed data to the (fixed) byte array which is prevented 958 * by ensureCapacity() and would also unnecessarily process 959 * further data that is discarded anyway. 960 * 961 * Since we are re-using the byte array of this object below, we 962 * must not call close() in e.g. a finally block either (with or 963 * without a call to def.end()). 964 */ 965 def.end(); 966 // could not make the value smaller than originally 967 // -> reset to starting count, write uncompressed 968 super.count = startCount; 969 try { 970 oos.writeTo(this); 971 // if the term is written as a compressed term, the output 972 // stream is closed, so we do this here, too 973 close(); 974 } catch (final IOException e2) { 975 throw new java.lang.IllegalArgumentException( 976 "Intermediate stream failed for Erlang object " + o); 977 } 978 } catch (final IOException e) { 979 throw new java.lang.IllegalArgumentException( 980 "Intermediate stream failed for Erlang object " + o); 981 } finally { 982 fixedSize = Integer.MAX_VALUE; 983 } 984 } 985 } 986 987 /** 988 * Write an arbitrary Erlang term to the stream. 989 * 990 * @param o 991 * the Erlang term to write. 992 */ 993 public void write_any(final OtpErlangObject o) { 994 // calls one of the above functions, depending on o 995 o.encode(this); 996 } 997 998 public void write_fun(final OtpErlangPid pid, final String module, 999 final long old_index, final int arity, final byte[] md5, 1000 final long index, final long uniq, final OtpErlangObject[] freeVars) { 1001 if (arity == -1) { 1002 write1(OtpExternal.funTag); 1003 write4BE(freeVars.length); 1004 pid.encode(this); 1005 write_atom(module); 1006 write_long(index); 1007 write_long(uniq); 1008 for (final OtpErlangObject fv : freeVars) { 1009 fv.encode(this); 1010 } 1011 } else { 1012 write1(OtpExternal.newFunTag); 1013 final int saveSizePos = getPos(); 1014 write4BE(0); // this is where we patch in the size 1015 write1(arity); 1016 writeN(md5); 1017 write4BE(index); 1018 write4BE(freeVars.length); 1019 write_atom(module); 1020 write_long(old_index); 1021 write_long(uniq); 1022 pid.encode(this); 1023 for (final OtpErlangObject fv : freeVars) { 1024 fv.encode(this); 1025 } 1026 poke4BE(saveSizePos, getPos() - saveSizePos); 1027 } 1028 } 1029 1030 public void write_external_fun(final String module, final String function, 1031 final int arity) { 1032 write1(OtpExternal.externalFunTag); 1033 write_atom(module); 1034 write_atom(function); 1035 write_long(arity); 1036 } 1037 1038 public void write_map_head(final int arity) { 1039 write1(OtpExternal.mapTag); 1040 write4BE(arity); 1041 } 1042 } 1043