package org.zaproxy.zap.utils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.Arrays;
import org.apache.log4j.Logger;
public class ByteBuilder {
private static final Logger logger = Logger.getLogger(ByteBuilder.class);
private byte[] array;
private int size;
public ByteBuilder() {
this(10);
}
public ByteBuilder(int capacity) {
array = new byte[capacity];
size = 0;
}
public ByteBuilder(byte[] array){
this.array = Arrays.copyOf(array, array.length*2);
size = array.length;
}
public void ensureCapacity(int capacity){
if (capacity > this.array.length){
this.array = Arrays.copyOf(array, capacity);
}
}
private void testAddition(int addition){
while (size+addition >= this.array.length){
this.array = Arrays.copyOf(array, this.array.length*2);
}
}
private void copyIntoArray(byte[] b){
for (int i = 0; i < b.length; i++) {
array[size+i] = b[i];
}
size += b.length;
}
public int capacity(){ return array.length; }
public int size(){ return size; }
public void truncate(int size) {
array = Arrays.copyOf(array, size);
}
public byte[] subSequence(int start, int end){
return Arrays.copyOfRange(array, start, end);
}
public byte[] toByteArray(){
return Arrays.copyOf(array, size);
}
@Override public String toString() {
return new String(array);
}
//all append() methods return this so we can chain calls together, of course :)
public ByteBuilder append(byte value){
testAddition(1);
this.array[size++] = value;
return this;
}
public ByteBuilder append(byte[] value){
return this.append(value, 0, value.length);
}
public ByteBuilder append(byte[] value, final int offset, final int len){
if (offset+len > value.length) throw new ArrayIndexOutOfBoundsException();
testAddition(len);
for (int i = 0; i < len; i++){
this.array[size+(i)] = value[i+offset];
}
size += len;
return this;
}
public ByteBuilder append(char value){
testAddition(1);
this.array[size++] = (byte)value;
return this;
}
public ByteBuilder append(char[] value){
return this.append(value, 0, value.length);
}
public ByteBuilder append(char[] value, final int offset, final int len){
if (offset+len > value.length) throw new ArrayIndexOutOfBoundsException();
testAddition(len);
for (int i = 0; i < len; i++){
this.array[size+(i)] = (byte)value[i+offset];
}
size += len;
return this;
}
public ByteBuilder append(boolean value){
testAddition(1);
this.array[size++] = (byte) ((value)?1:0);
return this;
}
public ByteBuilder append(short value){
final int LEN = 2; //int are 4 bytes long
testAddition(LEN);
{
byte[] b = new byte[LEN];
for (int i = 0; i < LEN; i++) {
int offset = (b.length - 1 - i) * 8;
b[i] = (byte) ((value >>> offset) & 0xFF);
}
copyIntoArray(b);
}
return this;
}
public ByteBuilder append(int value){
final int LEN = 4; //int are 4 bytes long
testAddition(LEN);
{
byte[] b = new byte[LEN];
for (int i = 0; i < LEN; i++) {
int offset = (b.length - 1 - i) * 8;
b[i] = (byte) ((value >>> offset) & 0xFF);
}
copyIntoArray(b);
}
return this;
}
public ByteBuilder append(long value){
final int LEN = 8; //long are 8 bytes long
testAddition(LEN);
{
byte[] b = new byte[LEN];
for (int i = 0; i < LEN; i++) {
int offset = (b.length - 1 - i) * 8;
b[i] = (byte) ((value >>> offset) & 0xFF);
}
copyIntoArray(b);
}
return this;
}
public ByteBuilder append(float value){
final int LEN = 4; //float are 4 bytes long
testAddition(LEN);
int intval = Float.floatToRawIntBits(value);
{
byte[] b = new byte[LEN];
for (int i = 0; i < LEN; i++) {
int offset = (b.length - 1 - i) * 8;
b[i] = (byte) ((intval >>> offset) & 0xFF);
}
copyIntoArray(b);
}
return this;
}
public ByteBuilder append(double value){
final int LEN = 8; //double are 8 bytes long
testAddition(LEN);
long intval = Double.doubleToRawLongBits(value);
{
byte[] b = new byte[LEN];
for (int i = 0; i < LEN; i++) {
int offset = (b.length - 1 - i) * 8;
b[i] = (byte) ((intval >>> offset) & 0xFF);
}
copyIntoArray(b);
}
return this;
}
public ByteBuilder append(Object value){
try {
//attempts to call a toByteStructure() method on the object, if it has it.
Method bytem = value.getClass().getMethod("toByteStructure", (Class<?>[])null);
byte[] array = (byte[]) bytem.invoke(value);
return this.append(array);
} catch (SecurityException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
// shouldn't happen but in case it does log it.
if (logger.isDebugEnabled()) {
logger.debug(e.getMessage(), e);
}
} catch (NoSuchMethodException e) {
// will happen, a lot
}
//if the above did not work, instead append the result of the toString() method.
return this.append(value.toString());
}
public ByteBuilder append(String value){
byte[] b = value.getBytes(Charset.defaultCharset());
testAddition(b.length+4);
return this.append(b.length).append(b);
}
public ByteBuilder append(StringBuffer value){
byte[] b = value.toString().getBytes(Charset.defaultCharset());
testAddition(b.length+4);
return this.append(b.length).append(b);
}
public ByteBuilder append(ByteBuilder value){
testAddition(value.size);
return this.append(value.array, 0, value.size);
}
///////// and now, appendSpecial() //////////
public ByteBuilder appendSpecial(long value, int length, boolean preserveNegative){
testAddition(length);
{
byte[] b = new byte[length];
for (int i = 0; i < length; i++) {
int offset = (b.length - 1 - i) * 8;
b[i] = (byte) ((value >>> offset) & 0xFF);
if (preserveNegative && i == 0){
if (value < 0){ //original value is negative
b[i] |= 0x80; // 1000 0000 - or'ed into the value to make it negative
} else { //original value is positive
b[i] &= 0x7F; // 0111 0000 - and'ed into the value to clear the negative bit
}
}
}
copyIntoArray(b);
}
return this;
}
}