/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 2000-2009. All Rights Reserved.
*
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* %CopyrightEnd%
*/
/**
* This is derived from com.ericsson.otp.erlang.OtpInputStream
*/
package erjang;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.EOFException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.MessageDigest;
import erjang.driver.IO;
/**
* Provides a stream for decoding Erlang terms from external format.
*
* <p>
* Note that this class is not synchronized, if you need synchronization you
* must provide it yourself.
*/
public class EInputStream extends ByteArrayInputStream {
public static int DECODE_INT_LISTS_AS_STRINGS = 1;
private final int flags;
private EAtom[] atom_cache_refs;
private boolean safeMode;
/**
* @param buf
*/
public EInputStream(final byte[] buf) {
this(buf, 0);
}
/**
* Create a stream from a buffer containing encoded Erlang terms.
*
* @param flags
*/
public EInputStream(final byte[] buf, final int flags) {
super(buf);
this.flags = flags;
}
/**
* Create a stream from a buffer containing encoded Erlang terms at the
* given offset and length.
*
* @param flags
*/
public EInputStream(final byte[] buf, final int offset, final int length,
final int flags) {
super(buf, offset, length);
this.flags = flags;
}
public void updateMessageDigest(MessageDigest digest, int offset, int len) {
digest.update(this.buf, offset, len);
}
/**
* Get the current position in the stream.
*
* @return the current position in the stream.
*/
public int getPos() {
return super.pos;
}
/**
* Set the current position in the stream.
*
* @param pos
* the position to move to in the stream. If pos indicates a
* position beyond the end of the stream, the position is move to
* the end of the stream instead. If pos is negative, the
* position is moved to the beginning of the stream instead.
*
* @return the previous position in the stream.
*/
public int setPos(int pos) {
final int oldpos = super.pos;
if (pos > super.count) {
pos = super.count;
} else if (pos < 0) {
pos = 0;
}
super.pos = pos;
return oldpos;
}
/**
* Read an array of bytes from the stream. The method reads at most
* buf.length bytes from the input stream.
*
* @return the number of bytes read.
*
* @exception IOException
* if the next byte cannot be read.
*/
public int readN(final byte[] buf) throws IOException {
return this.readN(buf, 0, buf.length);
}
/**
* Read an array of bytes from the stream. The method reads at most len
* bytes from the input stream into offset off of the buffer.
*
* @return the number of bytes read.
*
* @exception IOException
* if the next byte cannot be read.
*/
public int readN(final byte[] buf, final int off, final int len)
throws IOException {
if (len == 0 && available() == 0) {
return 0;
}
final int i = super.read(buf, off, len);
if (i < 0) {
throw new IOException("Cannot read from input stream");
}
return i;
}
/**
* Alias for peek1()
*/
public int peek() throws IOException {
return peek1();
}
/**
* Look ahead one position in the stream without consuming the byte found
* there.
*
* @return the next byte in the stream, as an integer.
*
* @exception IOException
* if the next byte cannot be read.
*/
public int peek1() throws IOException {
int i;
try {
i = super.buf[super.pos];
if (i < 0) {
i += 256;
}
return i;
} catch (final Exception e) {
throw new IOException("Cannot read from input stream");
}
}
public int peek1skip_version() throws IOException {
int i = peek1();
if (i == EExternal.versionTag) {
read1();
i = peek1();
}
return i;
}
/**
* Read a one byte integer from the stream.
*
* @return the byte read, as an integer.
*
* @exception IOException
* if the next byte cannot be read.
*/
public int read1() throws IOException {
int i;
i = super.read();
if (i < 0) {
throw new IOException("Cannot read from input stream");
}
return i;
}
public int read1skip_version() throws IOException {
int tag = read1();
if (tag == EExternal.versionTag) {
tag = read1();
}
return tag;
}
public void readFully(byte[] b) throws IOException {
int pos = 0;
while (pos < b.length) {
int read;
try {
read = super.read(b, pos, b.length-pos);
} catch (IndexOutOfBoundsException ioobe) {
read = 0;
}
if (read==0) throw new EOFException("Can't read enough from input stream");
pos += read;
}
}
/**
* Read a two byte big endian integer from the stream.
*
* @return the bytes read, converted from big endian to an integer.
*
* @exception IOException
* if the next byte cannot be read.
*/
public int read2BE() throws IOException {
final byte[] b = new byte[2];
readFully(b);
return (b[0] << 8 & 0xff00) + (b[1] & 0xff);
}
/**
* Read a four byte big endian integer from the stream.
*
* @return the bytes read, converted from big endian to an integer.
*
* @exception IOException
* if the next byte cannot be read.
*/
public int read4BE() throws IOException {
final byte[] b = new byte[4];
readFully(b);
return (b[0] << 24 & 0xff000000) + (b[1] << 16 & 0xff0000)
+ (b[2] << 8 & 0xff00) + (b[3] & 0xff);
}
/**
* Read a two byte little endian integer from the stream.
*
* @return the bytes read, converted from little endian to an integer.
*
* @exception IOException
* if the next byte cannot be read.
*/
public int read2LE() throws IOException {
final byte[] b = new byte[2];
readFully(b);
return (b[1] << 8 & 0xff00) + (b[0] & 0xff);
}
/**
* Read a four byte little endian integer from the stream.
*
* @return the bytes read, converted from little endian to an integer.
*
* @exception IOException
* if the next byte cannot be read.
*/
public int read4LE() throws IOException {
final byte[] b = new byte[4];
readFully(b);
return (b[3] << 24 & 0xff000000) + (b[2] << 16 & 0xff0000)
+ (b[1] << 8 & 0xff00) + (b[0] & 0xff);
}
/**
* Read a little endian integer from the stream.
*
* @param n
* the number of bytes to read
*
* @return the bytes read, converted from little endian to an integer.
*
* @exception IOException
* if the next byte cannot be read.
*/
public long readLE(int n) throws IOException {
final byte[] b = new byte[n];
try {
super.read(b);
} catch (final IOException e) {
throw new IOException("Cannot read from input stream");
}
;
long v = 0;
while (n-- > 0) {
v = v << 8 | (long) b[n] & 0xff;
}
return v;
}
/**
* Read a bigendian integer from the stream.
*
* @param n
* the number of bytes to read
*
* @return the bytes read, converted from big endian to an integer.
*
* @exception IOException
* if the next byte cannot be read.
*/
public long readBE(final int n) throws IOException {
final byte[] b = new byte[n];
try {
super.read(b);
} catch (final IOException e) {
throw new IOException("Cannot read from input stream");
}
;
long v = 0;
for (int i = 0; i < n; i++) {
v = v << 8 | (long) b[i] & 0xff;
}
return v;
}
/**
* Read an Erlang atom from the stream and interpret the value as a boolean.
*
* @return true if the atom at the current position in the stream contains
* the value 'true' (ignoring case), false otherwise.
*
* @exception IOException
* if the next term in the stream is not an atom.
*/
public boolean read_boolean() throws IOException {
return read_atom() == ERT.TRUE;
}
/**
* Read an Erlang atom from the stream.
*
* @return a String containing the value of the atom.
*
* @exception IOException
* if the next term in the stream is not an atom.
*/
public EAtom read_atom() throws IOException {
int tag;
int len;
byte[] strbuf;
String atom;
tag = read1skip_version();
if (tag == EExternal.atomCacheRef) {
int index = read1() & 0xff;
EAtom res = atom_cache_refs[index];
if (res == null) {
throw new IOException("no cached atom at "+index);
}
return res;
}
if (tag == EExternal.smallAtomTag) {
len = read1();
} else {
if (tag != EExternal.atomTag) {
throw new IOException("wrong tag encountered, expected "
+ EExternal.atomTag + ", got " + tag);
}
len = read2BE();
}
strbuf = new byte[len];
this.readN(strbuf);
char[] in = new char[len];
for (int i = 0; i < len; i++) {
in[i] = (char) (strbuf[i] & 0xff);
}
atom = new String(in);
if (atom.length() > EExternal.maxAtomLength) {
atom = atom.substring(0, EExternal.maxAtomLength);
}
if (safeMode) {
return EAtom.existing_atom(atom);
} else {
return EAtom.intern(atom);
}
}
/**
* Read an Erlang binary from the stream.
*
* @return a byte array containing the value of the binary.
*
* @exception IOException
* if the next term in the stream is not a binary.
*/
public byte[] read_binary() throws IOException {
int tag;
int len;
byte[] bin;
tag = read1skip_version();
if (tag != EExternal.binTag) {
throw new IOException("Wrong tag encountered, expected "
+ EExternal.binTag + ", got " + tag);
}
len = read4BE();
bin = new byte[len];
this.readN(bin);
return bin;
}
/**
* Read an Erlang bitstr from the stream.
*
* @param pad_bits
* an int array whose first element will be set to the number of
* pad bits in the last byte.
*
* @return a byte array containing the value of the bitstr.
*
* @exception IOException
* if the next term in the stream is not a bitstr.
*/
public byte[] read_bitstr(final int pad_bits[]) throws IOException {
int tag;
int len;
byte[] bin;
tag = read1skip_version();
if (tag != EExternal.bitBinTag) {
throw new IOException("Wrong tag encountered, expected "
+ EExternal.bitBinTag + ", got " + tag);
}
len = read4BE();
bin = new byte[len];
final int tail_bits = read1();
if (tail_bits < 0 || 7 < tail_bits) {
throw new IOException("Wrong tail bit count in bitstr: "
+ tail_bits);
}
if (len == 0 && tail_bits != 0) {
throw new IOException("Length 0 on bitstr with tail bit count: "
+ tail_bits);
}
this.readN(bin);
pad_bits[0] = 8 - tail_bits;
return bin;
}
/**
* Read an Erlang float from the stream.
*
* @return the float value.
*
* @exception IOException
* if the next term in the stream is not a float.
*/
public float read_float() throws IOException {
final double d = read_double();
return (float) d;
}
/**
* Read an Erlang float from the stream.
*
* @return the float value, as a double.
*
* @exception IOException
* if the next term in the stream is not a float.
*/
public double read_double() throws IOException {
int tag;
// parse the stream
tag = read1skip_version();
switch (tag) {
case EExternal.newFloatTag: {
return Double.longBitsToDouble(readBE(8));
}
case EExternal.floatTag: {
BigDecimal val;
int epos;
int exp;
final byte[] strbuf = new byte[31];
String str;
// get the string
this.readN(strbuf);
str = new String(strbuf, IO.ISO_LATIN_1);
// find the exponent prefix 'e' in the string
epos = str.indexOf('e', 0);
if (epos < 0) {
throw new IOException("Invalid float format: '" + str + "'");
}
// remove the sign from the exponent, if positive
String estr = str.substring(epos + 1).trim();
if (estr.substring(0, 1).equals("+")) {
estr = estr.substring(1);
}
// now put the mantissa and exponent together
exp = Integer.valueOf(estr).intValue();
val = new BigDecimal(str.substring(0, epos)).movePointRight(exp);
return val.doubleValue();
}
default:
throw new IOException("Wrong tag encountered, expected "
+ EExternal.newFloatTag + ", got " + tag);
}
}
/**
* Read one byte from the stream.
*
* @return the byte read.
*
* @exception IOException
* if the next byte cannot be read.
*/
public byte read_byte() throws IOException {
final long l = this.read_long(false);
final byte i = (byte) l;
if (l != i) {
throw new IOException("Value does not fit in byte: " + l);
}
return i;
}
/**
* Read a character from the stream.
*
* @return the character value.
*
* @exception IOException
* if the next term in the stream is not an integer that can
* be represented as a char.
*/
public char read_char() throws IOException {
final long l = this.read_long(true);
final char i = (char) l;
if (l != (i & 0xffffL)) {
throw new IOException("Value does not fit in char: " + l);
}
return i;
}
/**
* Read an unsigned integer from the stream.
*
* @return the integer value.
*
* @exception IOException
* if the next term in the stream can not be represented as a
* positive integer.
*/
public int read_uint() throws IOException {
final long l = this.read_long(true);
final int i = (int) l;
if (l != (i & 0xFFFFffffL)) {
throw new IOException("Value does not fit in uint: " + l);
}
return i;
}
/**
* Read an integer from the stream.
*
* @return the integer value.
*
* @exception IOException
* if the next term in the stream can not be represented as
* an integer.
*/
public int read_int() throws IOException {
final long l = this.read_long(false);
final int i = (int) l;
if (l != i) {
throw new IOException("Value does not fit in int: " + l);
}
return i;
}
/**
* Read an unsigned short from the stream.
*
* @return the short value.
*
* @exception IOException
* if the next term in the stream can not be represented as a
* positive short.
*/
public short read_ushort() throws IOException {
final long l = this.read_long(true);
final short i = (short) l;
if (l != (i & 0xffffL)) {
throw new IOException("Value does not fit in ushort: " + l);
}
return i;
}
/**
* Read a short from the stream.
*
* @return the short value.
*
* @exception IOException
* if the next term in the stream can not be represented as a
* short.
*/
public short read_short() throws IOException {
final long l = this.read_long(false);
final short i = (short) l;
if (l != i) {
throw new IOException("Value does not fit in short: " + l);
}
return i;
}
/**
* Read an unsigned long from the stream.
*
* @return the long value.
*
* @exception IOException
* if the next term in the stream can not be represented as a
* positive long.
*/
public long read_ulong() throws IOException {
return this.read_long(true);
}
/**
* Read a long from the stream.
*
* @return the long value.
*
* @exception IOException
* if the next term in the stream can not be represented as a
* long.
*/
public long read_long() throws IOException {
return this.read_long(false);
}
public long read_long(final boolean unsigned) throws IOException {
final byte[] b = read_integer_byte_array();
return EInputStream.byte_array_to_long(b, unsigned);
}
public EInteger read_tagged_integer() throws IOException {
int tag = read1skip_version();
switch (tag) {
case EExternal.smallIntTag: return new ESmall(read1());
case EExternal.intTag: return new ESmall(read4BE());
default: setPos(getPos()-1); return ERT.box(new BigInteger(read_integer_byte_array()));
} // switch
}
/**
* Read an integer from the stream.
*
* @return the value as a big endian 2's complement byte array.
*
* @exception IOException
* if the next term in the stream is not an integer.
*/
public byte[] read_integer_byte_array() throws IOException {
int tag;
byte[] nb;
tag = read1skip_version();
switch (tag) {
case EExternal.smallIntTag:
nb = new byte[2];
nb[0] = 0;
nb[1] = (byte) read1();
break;
case EExternal.intTag:
nb = new byte[4];
if (this.readN(nb) != 4) { // Big endian
throw new IOException("Cannot read from intput stream");
}
break;
case EExternal.smallBigTag:
case EExternal.largeBigTag:
int arity;
int sign;
if (tag == EExternal.smallBigTag) {
arity = read1();
sign = read1();
} else {
arity = read4BE();
sign = read1();
if (arity + 1 < 0) {
throw new IOException(
"Value of largeBig does not fit in BigInteger, arity "
+ arity + " sign " + sign);
}
}
nb = new byte[arity + 1];
// Value is read as little endian. The big end is augumented
// with one zero byte to make the value 2's complement positive.
if (this.readN(nb, 0, arity) != arity) {
throw new IOException("Cannot read from intput stream");
}
// Reverse the array to make it big endian.
for (int i = 0, j = nb.length; i < j--; i++) {
// Swap [i] with [j]
final byte b = nb[i];
nb[i] = nb[j];
nb[j] = b;
}
if (sign != 0) {
// 2's complement negate the big endian value in the array
int c = 1; // Carry
for (int j = nb.length; j-- > 0;) {
c = (~nb[j] & 0xFF) + c;
nb[j] = (byte) c;
c >>= 8;
}
}
break;
default:
throw new IOException("Not valid integer tag: " + tag);
}
return nb;
}
public static long byte_array_to_long(final byte[] b, final boolean unsigned)
throws IOException {
long v;
switch (b.length) {
case 0:
v = 0;
break;
case 2:
v = ((b[0] & 0xFF) << 8) + (b[1] & 0xFF);
v = (short) v; // Sign extend
if (v < 0 && unsigned) {
throw new IOException("Value not unsigned: " + v);
}
break;
case 4:
v = ((b[0] & 0xFF) << 24) + ((b[1] & 0xFF) << 16)
+ ((b[2] & 0xFF) << 8) + (b[3] & 0xFF);
v = (int) v; // Sign extend
if (v < 0 && unsigned) {
throw new IOException("Value not unsigned: " + v);
}
break;
default:
int i = 0;
final byte c = b[i];
// Skip non-essential leading bytes
if (unsigned) {
if (c < 0) {
throw new IOException("Value not unsigned: " + b);
}
while (b[i] == 0) {
i++; // Skip leading zero sign bytes
}
} else {
if (c == 0 || c == -1) { // Leading sign byte
i = 1;
// Skip all leading sign bytes
while (i < b.length && b[i] == c) {
i++;
}
if (i < b.length) {
// Check first non-sign byte to see if its sign
// matches the whole number's sign. If not one more
// byte is needed to represent the value.
if (((c ^ b[i]) & 0x80) != 0) {
i--;
}
}
}
}
if (b.length - i > 8) {
// More than 64 bits of value
throw new IOException("Value does not fit in long: " + b);
}
// Convert the necessary bytes
for (v = c < 0 ? -1 : 0; i < b.length; i++) {
v = v << 8 | b[i] & 0xFF;
}
}
return v;
}
/**
* Read a list header from the stream.
*
* @return the arity of the list.
*
* @exception IOException
* if the next term in the stream is not a list.
*/
public int read_list_head() throws IOException {
int arity = 0;
final int tag = read1skip_version();
switch (tag) {
case EExternal.nilTag:
arity = 0;
break;
case EExternal.stringTag:
arity = read2BE();
break;
case EExternal.listTag:
arity = read4BE();
break;
default:
throw new IOException("Not valid list tag: " + tag);
}
return arity;
}
public int read_map_head() throws IOException {
int arity = 0;
final int tag = read1skip_version();
// decode the map header and get arity
switch (tag) {
case EExternal.mapTag:
arity = read4BE();
break;
default:
throw new IOException("Not valid map tag: " + tag);
}
return arity;
}
/**
* Read a tuple header from the stream.
*
* @return the arity of the tuple.
*
* @exception IOException
* if the next term in the stream is not a tuple.
*/
public int read_tuple_head() throws IOException {
int arity = 0;
final int tag = read1skip_version();
// decode the tuple header and get arity
switch (tag) {
case EExternal.smallTupleTag:
arity = read1();
break;
case EExternal.largeTupleTag:
arity = read4BE();
break;
default:
throw new IOException("Not valid tuple tag: " + tag);
}
return arity;
}
/**
* Read an empty list from the stream.
*
* @return zero (the arity of the list).
*
* @exception IOException
* if the next term in the stream is not an empty list.
*/
public int read_nil() throws IOException {
int arity = 0;
final int tag = read1skip_version();
switch (tag) {
case EExternal.nilTag:
arity = 0;
break;
default:
throw new IOException("Not valid nil tag: " + tag);
}
return arity;
}
/**
* Read an Erlang PID from the stream.
*
* @return the value of the PID.
*
* @exception IOException
* if the next term in the stream is not an Erlang PID.
*/
public EPID read_pid() throws IOException {
EAtom node;
int id;
int serial;
int creation;
int tag;
tag = read1skip_version();
if (tag != EExternal.pidTag) {
throw new IOException("Wrong tag encountered, expected "
+ EExternal.pidTag + ", got " + tag);
}
node = read_atom();
id = read4BE() & 0x7fff; // 15 bits
serial = read4BE() & 0x1fff; // 13 bits
creation = read1() & 0x03; // 2 bits
return EPID.make(node, id, serial, creation);
}
/**
* Read an Erlang port from the stream.
*
* @return the value of the port.
*
* @exception IOException
* if the next term in the stream is not an Erlang port.
*/
public EPort read_port() throws IOException {
EAtom node;
int id;
int creation;
int tag;
tag = read1skip_version();
if (tag != EExternal.portTag) {
throw new IOException("Wrong tag encountered, expected "
+ EExternal.portTag + ", got " + tag);
}
node = read_atom();
id = read4BE() & 0xfffffff; // 28 bits
creation = read1() & 0x03; // 2 bits
return EPort.make(node, id, creation);
}
/**
* Read an Erlang reference from the stream.
*
* @return the value of the reference
*
* @exception IOException
* if the next term in the stream is not an Erlang reference.
*/
public ERef read_ref() throws IOException {
EAtom node;
int id;
int creation;
int tag;
tag = read1skip_version();
switch (tag) {
case EExternal.refTag:
node = read_atom();
id = read4BE() & 0x3ffff; // 18 bits
creation = read1() & 0x03; // 2 bits
return new ERef(node, id, creation);
case EExternal.newRefTag:
final int arity = read2BE();
node = read_atom();
creation = read1() & 0x03; // 2 bits
final int[] ids = new int[arity];
for (int i = 0; i < arity; i++) {
ids[i] = read4BE();
}
ids[0] &= 0x3ffff; // first id gets truncated to 18 bits
return new ERef(node, ids, creation);
default:
throw new IOException("Wrong tag encountered, expected ref, got "
+ tag);
}
}
public EFun read_fun() throws IOException {
final int tag = read1skip_version();
if (tag == EExternal.funTag) {
final int nFreeVars = read4BE();
final EPID pid = read_pid();
final EAtom module = read_atom();
final long index = read_long();
final long uniq = read_long();
final EObject[] freeVars = new EObject[nFreeVars];
for (int i = 0; i < nFreeVars; ++i) {
freeVars[i] = read_any();
}
return EModuleManager.resolve(pid, module, (int)uniq, (int)index, freeVars);
} else if (tag == EExternal.newFunTag) {
@SuppressWarnings("unused")
final int n = read4BE(); // size
final int arity = read1();
final byte[] md5 = new byte[16];
readN(md5);
final int index = read4BE();
final int nFreeVars = read4BE();
final EAtom module = read_atom();
final long oldIndex = read_long();
final long uniq = read_long();
final EPID pid = read_pid();
final EObject[] freeVars = new EObject[nFreeVars];
for (int i = 0; i < nFreeVars; ++i) {
freeVars[i] = read_any();
}
return EModuleManager.resolve(pid, module, new EBinary(md5), index, (int)uniq, (int)oldIndex, arity, freeVars);
} else if (tag == EExternal.externalFunTag) {
final EAtom module = read_atom();
final EAtom function = read_atom();
final int arity = (int) read_long();
return EModuleManager.resolve(new FunID(module,function,arity));
} else {
throw new IOException("Wrong tag encountered, expected fun, got "
+ tag);
}
}
public EFun read_external_fun() throws IOException {
final int tag = read1skip_version();
if (tag != EExternal.externalFunTag) {
throw new IOException(
"Wrong tag encountered, expected external fun, got " + tag);
}
final EAtom module = read_atom();
final EAtom function = read_atom();
final int arity = (int) read_long();
return EModuleManager.resolve(new FunID(module,function,arity));
}
/**
* Read a string from the stream.
*
* @return the value of the string.
*
* @exception IOException
* if the next term in the stream is not a string.
*/
public ESeq read_string() throws IOException {
int tag;
int len;
byte[] strbuf;
int[] intbuf;
tag = read1skip_version();
switch (tag) {
case EExternal.stringTag:
len = read2BE();
strbuf = new byte[len];
this.readN(strbuf);
return EString.make(strbuf);
case EExternal.nilTag:
return ERT.NIL;
case EExternal.listTag: // List when unicode +
len = read4BE();
intbuf = new int[len];
boolean unicode = false;
for (int i = 0; i < len; i++) {
intbuf[i] = read_int();
unicode |= intbuf[i] > 0xff;
if (!EString.isValidCodePoint(intbuf[i])) {
throw new IOException("Invalid CodePoint: " + intbuf[i]);
}
}
read_nil();
return EList.make(intbuf);
default:
throw new IOException("Wrong tag encountered, expected "
+ EExternal.stringTag + " or " + EExternal.listTag
+ ", got " + tag);
}
}
/**
* Read a compressed term from the stream
*
* @return the resulting uncompressed term.
*
* @exception IOException
* if the next term in the stream is not a compressed term.
*/
public EObject read_compressed() throws IOException {
final int tag = read1skip_version();
if (tag != EExternal.compressedTag) {
throw new IOException("Wrong tag encountered, expected "
+ EExternal.compressedTag + ", got " + tag);
}
final byte[] buf = read_size_and_inflate();
final EInputStream ois = new EInputStream(buf, flags);
return ois.read_any();
}
public byte[] read_size_and_inflate() throws IOException {
final int size = read4BE();
final byte[] buf = new byte[size];
final java.util.zip.Inflater inflater = new java.util.zip.Inflater();
final java.util.zip.InflaterInputStream is = new java.util.zip.InflaterInputStream(this, inflater);
try {
int pos = 0;
while (pos<size) { // Inflate fully
final int dsize = is.read(buf, pos, size-pos);
if (dsize==0) break;
pos += dsize;
}
if (pos < size) {
throw new IOException("Decompression gave " + pos
+ " bytes, not " + size);
}
// Back up if the inflater read ahead:
int read_ahead = inflater.getRemaining();
setPos(getPos() - read_ahead);
} finally {
is.close();
}
return buf;
}
/**
* Read an arbitrary Erlang term from the stream.
*
* @return the Erlang term.
*
* @exception IOException
* if the stream does not contain a known Erlang type at the
* next position.
*/
public EObject read_any() throws IOException {
// calls one of the above functions, depending on o
final int tag = peek1skip_version();
switch (tag) {
case EExternal.smallIntTag:
case EExternal.intTag:
case EExternal.smallBigTag:
case EExternal.largeBigTag:
return EInteger.read(this);
case EExternal.atomCacheRef:
case EExternal.atomTag:
case EExternal.smallAtomTag:
return EAtom.read(this);
case EExternal.floatTag:
case EExternal.newFloatTag:
return EDouble.read(this);
case EExternal.refTag:
case EExternal.newRefTag:
return ERef.read(this);
case EExternal.portTag:
return EPort.read(this);
case EExternal.pidTag:
return EPID.read(this);
case EExternal.stringTag:
return EString.read(this);
case EExternal.listTag:
case EExternal.nilTag:
if ((flags & DECODE_INT_LISTS_AS_STRINGS) != 0) {
final int savePos = getPos();
try {
return EString.read(this);
} catch (final IOException e) {
}
setPos(savePos);
}
return EList.read(this);
case EExternal.smallTupleTag:
case EExternal.largeTupleTag:
return ETuple.read(this);
case EExternal.binTag:
return EBinary.read(this);
case EExternal.bitBinTag:
return EBitString.read(this);
case EExternal.compressedTag:
return read_compressed();
case EExternal.newFunTag:
case EExternal.funTag:
return EFun.read(this);
case EExternal.externalFunTag:
return read_external_fun();
case EExternal.mapTag:
return EMap.read(this);
default:
throw new IOException("Unknown data type: " + tag + " at position " + getPos());
}
}
public void setAtomCacheRefs(EAtom[] atomCacheRefs) {
this.atom_cache_refs = atomCacheRefs;
}
public void setSafe(boolean safeMode) {
this.safeMode = safeMode;
}
}