/**
* This file is part of Erjang - A JVM-based Erlang VM
*
* Copyright (c) 2009 by Trifork
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package erjang;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Set;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import erjang.m.ets.EMatchContext;
import erjang.m.ets.EPattern;
import erjang.m.ets.ETermPattern;
/**
*
*/
public class EBitString extends EObject {
public static final Type EBITSTRING_TYPE = Type.getType(EBitString.class);
public static final String EBITSTRING_NAME = EBITSTRING_TYPE
.getInternalName();
protected final byte[] data;
private final int data_offset;
int byte_size;
protected final int extra_bits;
//private final long bits;
//protected final long bitOff = 0;
protected EBitString(byte[] data) {
this(data.clone(), 0, data.length, 0);
}
@Override
public int hashCode() {
int h = 0;
for (int i = 0; i < byte_size; i++) {
h = h * 31 + data[data_offset + i];
}
if (extra_bits != 0) {
int val = data[data_offset + byte_size] >>> (8-extra_bits);
h = h * 31 + val;
}
return h;
}
/**
* @return the bits
*/
public long bitSize() {
return byteSize() * 8L + extra_bits;
}
/**
* @return number of bytes needed to hold this bit string (byteSize + (extrabits?1:0))
* */
protected int dataByteSize() {
return byteSize() + (extra_bits > 0 ? 1 : 0);
}
public EBitString testBitString() {
return this;
}
/** returns true if this bitstring matches the give <code>matcher</code>. */
public boolean match(ETermPattern matcher, EMatchContext r) {
return matcher.match(this, r);
}
public EBinary testBinary() {
if (isBinary()) {
assert false : "EBitString is Binary and testBinary is not overridden.";
// this should never happen
return new EBinary(toByteArray());
}
return null;
}
@Override
int cmp_order() {
return CMP_ORDER_BITSTRING;
}
@Override
public Type emit_const(MethodVisitor fa) {
char[] chs = new char[dataByteSize()];
for (int byteIdx = 0; byteIdx < byteSize(); byteIdx += 1) {
chs[byteIdx] = (char)(data[byteIdx] & 0xff);
}
if (extra_bits != 0) {
int rest = intBitsAt(byteSize()*8, extra_bits);
rest <<= (8-extra_bits);
chs[byteSize()] = (char)rest;
}
String str = new String(chs);
fa.visitLdcInsn(str);
fa.visitLdcInsn(new Integer(extra_bits));
fa.visitMethodInsn(Opcodes.INVOKESTATIC, EBITSTRING_NAME, "make",
"(Ljava/lang/String;I)L" + EBITSTRING_NAME + ";");
return EBITSTRING_TYPE;
}
/** called by generated code to emit a constant BitString.
* If extra > 0, then the last char of str contains the extra bits */
public static EBitString make(String str, int extra) {
int in_len = str.length();
byte[] data = new byte[in_len];
for (int i = 0; i < in_len; i++) {
data[i] = (byte) str.charAt(i);
}
int data_len = in_len - (extra>0 ? 1 : 0);
return EBitString.make(data, 0, data_len, extra);
}
@Override
public boolean equalsExactly(EObject rhs) {
if (rhs.cmp_order() != CMP_ORDER_BITSTRING)
return false;
EBitString ebs = (EBitString) rhs;
if (byteSize() != ebs.byteSize())
return false;
if (extra_bits != ebs.extra_bits)
return false;
int byteOffset = byteOffset();
int ebsByteOffset = ebs.byteOffset();
for (int i = 0; i < byte_size; i++) {
if (data[byteOffset + i] != ebs.data[ebsByteOffset + i])
return false;
}
if (extra_bits != 0) {
int myExtraBits = intBitsAt(8L*byte_size, extra_bits);
int hisExtraBits = ebs.intBitsAt(8L*byte_size, extra_bits);
return myExtraBits == hisExtraBits;
} else {
return true;
}
}
@Override
int compare_same(EObject rhs) {
EBitString ebs = (EBitString) rhs;
long bc1 = bitSize();
long bc2 = ebs.bitSize();
long limit = Math.min(bc1, bc2);
for (int pos = 0; pos < limit; pos += 8) {
int rest = limit-pos>8 ? 8 : (int)(limit-pos);
int oc1 = 0xff & intBitsAt(pos, rest);
int oc2 = 0xff & ebs.intBitsAt(pos, rest);
if (oc1 == oc2)
continue;
if (oc1 < oc2)
return -1;
if (oc1 > oc2)
return 1;
}
if (bc1 == bc2)
return 0;
if (bc1 < bc2)
return -1;
else
return 1;
}
public static EBitString make(byte[] data, int byteOff, int byteLength, int extra_bits) {
if (extra_bits == 0) {
return new EBinary(data, byteOff, byteLength);
} else {
return new EBitString(data, byteOff, byteLength, extra_bits);
}
}
public static EBitString makeByteOffsetTail(EBitString org, int byteOff) {
return EBitString.make(org.data, org.data_offset+byteOff, org.byte_size-byteOff, org.extra_bits);
}
protected EBitString(byte[] data, int byte_off, int byte_len, int extra_bits) {
this.data = data;
this.data_offset = byte_off;
this.byte_size = byte_len;
this.extra_bits = extra_bits;
if (byte_size < 0){
throw new IllegalArgumentException();
}
if (data.length < byte_off + byte_len + (extra_bits>0?1:0)) {
throw new IllegalArgumentException();
}
if (extra_bits<0 || extra_bits>=8) {
throw new IllegalArgumentException();
}
}
public boolean isBinary() {
return extra_bits == 0;
}
public int octetAt(int byteIndex) {
return data[byteOffset() + byteIndex] & 0xff;
}
public int uint16_at(int byteIndex, int flags) {
int b1 = data[byteOffset() + byteIndex] & 0xff;
int b2 = data[byteOffset() + byteIndex + 1] & 0xff;
if ((flags & EBinMatchState.BSF_LITTLE) > 0)
return b1 + (b2 << 8);
else
return (b1 << 8) + b2;
}
public int int32_at(int byteIndex, int flags) {
int b1 = data[byteOffset() + byteIndex] & 0xff;
int b2 = data[byteOffset() + byteIndex + 1] & 0xff;
int b3 = data[byteOffset() + byteIndex + 2] & 0xff;
int b4 = data[byteOffset() + byteIndex + 3] & 0xff;
if ((flags & EBinMatchState.BSF_LITTLE) > 0)
return b1 + (b2 << 8) + (b3 << 16) + (b4 << 24);
else
return (b1 << 24) + (b2 << 16) + (b3 << 8) + b4;
}
public EBitString substring(long bitOff) {
return substring(bitOff, bitSize() - bitOff);
}
public EBitString substring(long bitOff, long bit_len) {
if (bitOff < 0 || bitOff + bit_len > bitSize()) {
throw new IllegalArgumentException("offset out of range");
}
bitOff += byteOffset() * 8;
int out_full_bytes = (int) (bit_len/8);
int extra = (int) (bit_len%8);
if (0 == (bitOff % 8)) {
return EBitString.make(data,
(int)(bitOff/8),
out_full_bytes, extra);
}
int out_bytes = (int) (out_full_bytes + (extra==0 ? 0 : 1));
byte[] res = new byte[out_bytes];
for (int i = 0; i < out_full_bytes; i++) {
res[i] = (byte) intBitsAt(bitOff + i*8, 8);
}
if (extra != 0) {
res[(int) out_full_bytes] =
(byte) (intBitsAt(bitOff + bit_len - extra, extra) << (8-extra));
}
return EBitString.make(res, 0, (int) out_full_bytes, extra);
}
public int bitAt(long bitPos) {
if (bitPos < 0 || bitPos >= bitSize()) {
throw new IllegalArgumentException("bit index out of range");
}
bitPos += byteOffset() * 8;
int data_byte = (int) data[(int) (bitPos >>> 3)];
int shift = 7 - (int)(bitPos & 0x07);
int bit = 0x01 & (data_byte >> shift);
return bit;
}
public int intBitsAt(long bitPos, int bitLength) {
if (bitPos + bitLength > this.bitSize()) {
throw new IllegalArgumentException(
"reading beyond end of BitString");
}
if (bitLength < 0 || bitLength > 32)
throw new IllegalArgumentException(
"this method can only get 32 bits");
bitPos += byteOffset() * 8;
int res = 0;
// first, get the right-most bits from data[bitPos/8]
if ((bitPos & 0x07) != 0) {
// how many bits from this byte?
int len = 8 - (int)(bitPos & 0x07);
// the byte
int val = 0x0ff & (int) data[(int) (bitPos >> 3)];
res = val & ((1 << len) - 1);
if (bitLength < len) {
res >>>= (len - bitLength);
return res;
}
bitLength -= len;
bitPos += len;
}
assert ((bitPos & 0x07) == 0);
// we're getting bytes
int pos = (int) (bitPos >> 3);
while (bitLength > 7) {
res <<= 8;
res |= 0x0ff & (int) data[pos++];
bitPos += 8;
bitLength -= 8;
}
assert (bitLength < 8);
// finally, get the left-most bits from data[bitPos/8]
if (bitLength != 0) {
// how many bits from this byte?
int len = bitLength;
// the byte
int val = 0x0ff & (int) data[pos];
res <<= bitLength;
res |= val >> (8 - len);
bitLength -= len;
bitPos += len;
}
assert (bitLength == 0);
return res;
}
public int intLittleEndianBitsAt(long bitPos, int bitLength) {
if (bitPos + bitLength > this.bitSize()) {
throw new IllegalArgumentException(
"reading beyond end of BitString");
}
if (bitLength < 0 || bitLength > 32)
throw new IllegalArgumentException(
"this method can only get 32 bits");
bitPos += byteOffset() * 8;
int res = 0;
int res_offset = 0;
// first, get the left-most bits from data[bitPos/8]
if ((bitPos & 0x07) != 0) {
// how many bits from this byte?
int len = 8 - (int)(bitPos & 0x07);
// the byte
int val = data[(int) (bitPos >> 3)] & 0x0ff;
res = val & ((1 << len) - 1);
if (bitLength < len) {
res >>>= (len - bitLength);
return res;
}
res_offset += len;
bitLength -= len;
bitPos += len;
}
assert ((bitPos & 0x07) == 0);
// we're getting bytes
int pos = (int) (bitPos >> 3);
while (bitLength > 7) {
res |= (data[pos++] & 0x0ff) << res_offset;
res_offset += 8;
bitPos += 8;
bitLength -= 8;
}
assert (bitLength < 8);
// finally, get the left-most bits from data[bitPos/8]
if (bitLength != 0) {
// how many bits from this byte?
int len = bitLength;
// the byte
int val = 0x0ff & (int) data[(int) (bitPos >> 3)];
res |= (val >> (8 - len)) << res_offset;
res_offset += len;
bitLength -= len;
bitPos += len;
}
assert (bitLength == 0);
return res;
}
/** Sign extend value of size bits.
* Assumes that bits above sign are zero.
* @see http://www-graphics.stanford.edu/~seander/bithacks.html#VariableSignExtend
* */
static int signExtend(int val, int bits) {
// Simpler (but slower?):
// int nonbits = 32-bits;
// return (val << nonbits) >> nonbits;
if (bits==32) return val;
int r; // resulting sign-extended number
int m = 1 << (bits - 1); // mask can be pre-computed if b is fixed
// val = val & ((1 << bits) - 1); // (Skip this if bits in x above position b are already zero.)
r = (val ^ m) - m;
return r;
}
static long signExtend(long val, int bits) {
// Simpler (but slower?):
// int nonbits = 64-bits;
// return (val << nonbits) >> nonbits;
if (bits==64) return val;
long r; // resulting sign-extended number
long m = 1L << (bits - 1); // mask can be pre-computed if b is fixed
// val = val & ((1 << bits) - 1); // (Skip this if bits in x above position b are already zero.)
r = (val ^ m) - m;
return r;
}
public byte byteAt(int bitPos) {
return (byte) intBitsAt(bitPos, 8);
}
public double doubleAt(int bitPos) {
return Double.longBitsToDouble(longBitsAt(bitPos, 64));
}
public double floatAt(int bitPos) {
return Float.intBitsToFloat(intBitsAt(bitPos, 32));
}
public long longBitsAt(long bitPos, int bitLength) {
if (bitPos + bitLength > this.bitSize()) {
throw new IllegalArgumentException(
"reading beyond end of BitString");
}
if (bitLength < 0 || bitLength > 64)
throw new IllegalArgumentException(
"this method can only get 64 bits");
long res = 0;
bitPos += byteOffset() * 8;
// first, get the right-most bits from data[bitPos/8]
if ((bitPos & 0x07) != 0) {
// how many bits from this byte?
int len = 8 - (int)(bitPos & 0x07);
// the byte
int val = 0x0ff & (int) data[(int) (bitPos >> 3)];
res = val & ((1 << len) - 1);
// are we looking for less that len bits?
if (bitLength < len) {
res >>>= (len - bitLength);
return res;
}
bitLength -= len;
bitPos += len;
}
assert ((bitPos & 0x07) == 0);
// we're getting bytes
int pos = (int) (bitPos >> 3);
while (bitLength > 7) {
res <<= 8;
res |= 0x0ff & (int) data[pos++];
bitPos += 8;
bitLength -= 8;
}
assert (bitLength < 8);
assert ((bitPos & 0x07) == 0);
// finally, get the left-most bits from data[bitPos/8]
if (bitLength != 0) {
// how many bits from this byte?
int len = bitLength;
// the byte
int val = 0x0ff & (int) data[(int) (bitPos >> 3)];
res <<= bitLength;
res |= val >> (8 - len);
bitLength -= len;
bitPos += len;
}
assert (bitLength == 0);
return res;
}
public long longLittleEndianBitsAt(long bitPos, int bitLength) {
if (bitPos + bitLength > this.bitSize()) {
throw new IllegalArgumentException(
"reading beyond end of BitString");
}
if (bitLength < 0 || bitLength > 64)
throw new IllegalArgumentException(
"this method can only get 64 bits");
long res = 0;
int res_offset = 0;
bitPos += byteOffset() * 8;
// first, get the right-most bits from data[bitPos/8]
if ((bitPos & 0x07) != 0) {
// how many bits from this byte?
int len = 8 - (int)(bitPos & 0x07);
// the byte
int val = data[(int) (bitPos >> 3)] & 0x0ff;
res = val & ((1 << len) - 1);
// are we looking for less that len bits?
if (bitLength < len) {
res >>>= (len - bitLength);
return res;
}
res_offset += len;
bitLength -= len;
bitPos += len;
}
assert ((bitPos & 0x07) == 0);
// we're getting bytes
int pos = (int) (bitPos >> 3);
while (bitLength >= 8) {
res |= ((long)(data[pos++] & 0x0ff)) << res_offset;
res_offset += 8;
bitPos += 8;
bitLength -= 8;
}
assert (bitLength < 8);
assert ((bitPos & 0x07) == 0);
// finally, get the left-most bits from data[bitPos/8]
if (bitLength != 0) {
// how many bits from this byte?
int len = bitLength;
// the byte
int val = data[pos] & 0x0ff;
res |= (long)(val >> (8 - len)) << res_offset;
res_offset += len;
bitLength -= len;
bitPos += len;
}
assert (bitLength == 0);
return res;
}
public EObject toList() {
EObject res = ERT.NIL;
int extra = (int) ((bitSize() % 8));
long pos;
if ((bitSize() % 8) == 0) {
pos = bitSize() - 8;
} else {
res = res.cons( substring(bitSize() - extra) );
pos = bitSize() - extra - 8;
}
while (pos >= 0) {
res = res.cons(ERT.box(octetAt((int)(pos/8))));
pos -= 8;
}
return res;
}
@Override
public String toString() {
foo: if (extra_bits == 0) {
StringBuilder sb = new StringBuilder("<<\"");
for (int i = 0; i < bitSize(); i += 8) {
char ch = (char) (0xff & intBitsAt(i, 8));
if (ch < ' ' || ch > '~')
break foo;
sb.append((char) ch);
}
sb.append("\">>");
return sb.toString();
}
StringBuilder sb = new StringBuilder("<<");
int i = 0;
long max = Math.min(bitSize()-8, 20*8);
for (; i < max; i += 8) {
sb.append(0xff & intBitsAt(i, 8));
sb.append(',');
}
if (max != bitSize()-8) { sb.append("...,"); i = (int) (bitSize()-8); }
int lastBitLength = (int) (bitSize() - i);
sb.append(0xff & intBitsAt(i, lastBitLength));
if (lastBitLength != 8) {
sb.append(':').append(lastBitLength);
}
sb.append(">>");
return sb.toString();
}
/**
* @return
*/
public byte[] toByteArray() {
byte[] result = new byte[dataByteSize()];
System.arraycopy(data, byteOffset(), result, 0, dataByteSize());
return result;
}
@Override
public void visitIOList(EIOListVisitor out) throws ErlangError {
if (byteSize() > 0) {
out.visit(data, byteOffset(), byteSize());
}
if (extra_bits != 0) {
long bitsPos = (8 * byteSize());
int bits = (0xff & intBitsAt(bitsPos, extra_bits));
out.visitBits( bits, extra_bits );
}
}
/**
* @return true if successful
*/
@Override
public boolean collectIOList(List<ByteBuffer> out) {
if (extra_bits != 0)
return false;
if (byteSize() > 0) {
out.add(ByteBuffer.wrap(data, byteOffset(), byteSize()));
}
return true;
}
/**
* @param eInputStream
* @return
*/
public static EBitString read(EInputStream eInputStream) throws IOException {
int[] pad_bits = new int[1];
byte[] data = eInputStream.read_bitstr(pad_bits);
int extra_bits = 8 - pad_bits[0];
if (extra_bits==8) {
return new EBinary(data); //TODO: use a make().
} else {
int len = data.length-1;
return make(data, 0, data.length-1, extra_bits);
}
}
protected int byteOffset() {
return data_offset;
}
public int byteSize() {
return byte_size;
}
public int totalByteSize() {
return byte_size + (extra_bits>0 ? 1 : 0);
}
@Override
public ESeq collectCharList(CharCollector out, ESeq rest)
throws CharCollector.CollectingException,
CharCollector.InvalidElementException,
IOException
{
if (extra_bits != 0)
throw new CharCollector.InvalidElementException();
try {
return out.addBinary(data, data_offset, byte_size, rest);
} catch (CharCollector.PartialDecodingException e) {
int n = e.inputPos;
throw new CharCollector.CollectingException(EBitString.make(data, data_offset+n, byte_size-n, extra_bits));
}
}
@Override
public void encode(EOutputStream eos) {
// TODO: Avoid copying.
eos.write_bitstr(toByteArray(), extra_bits==0 ? 0 : 8-extra_bits);
}
@Override
public ETermPattern compileMatch(Set<Integer> out) {
return EPattern.compilePattern(this, out);
}
}