Skip to content

Instantly share code, notes, and snippets.

@Chase-san
Created March 19, 2012 10:16
Show Gist options
  • Save Chase-san/2106501 to your computer and use it in GitHub Desktop.
Save Chase-san/2106501 to your computer and use it in GitHub Desktop.
The Pack Class
package cs.pack;
import java.util.ArrayList;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import cs.pack.converters.*;
/**
* This is a type of unsafe pack/unpack functionality for java. PLEASE KEEP IN
* MIND, java is type safe, and has much security and access restrictions. It is
* by definition safe, this just emulates the functionality via fancy
* reflection/recursion/iteration.
*
* Because of this, there is zero padding! If you want padding, you have to insert it manually.
*
* True unsafe operation would require a more advanced bytecode manipulation,
* which is beyond the scope of this class.
*
* This class is limited and can only handle a handful of data types.
*
* It will only do public fields unless told to attempt otherwise, and will do
* recursive classes, arrays, etc.
*
* @author Chase
*
*/
public class Pack {
private ArrayList<Converter> converters = new ArrayList<Converter>();
private boolean doSecure = false;
private int level = 0;
/**
* Initializes with default options.
*/
public Pack() {
converters.add(new BooleanConverter());
converters.add(new CharacterConverter());
converters.add(new FloatConverter());
converters.add(new DoubleConverter());
converters.add(new ByteConverter());
converters.add(new ShortConverter());
converters.add(new IntegerConverter());
converters.add(new LongConverter());
}
/**
* Determines if the packer should attempt to pack non-public fields.
*
* @param dopack
*/
public void setPackSecure(boolean dopack) {
doSecure = dopack;
}
/**
* Unpacks the byte array into the given object.
*
* If the object is immutable, it may very well be returned, with the dest
* acting only as a template for it.
*/
public Object unpack(Object dest, ByteBuffer bbuf) {
level = 0;
return _unpack(dest, bbuf);
}
private Object _unpack(Object dest, ByteBuffer bbuf) {
if (dest.getClass().isArray()) {
unpackArray(dest, bbuf);
return dest;
}
if (level == 0) {
Class<?> type = dest.getClass();
try {
for (Converter c : converters) {
if (c.handles(type)) {
return c.toObject(null, dest, bbuf);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
++level;
Field[] fld = null;
if (doSecure) {
fld = dest.getClass().getDeclaredFields();
} else {
// Note: Safer (public only)
fld = dest.getClass().getFields();
}
for (Field f : fld) {
boolean isa = f.isAccessible();
try {
if (doSecure)
f.setAccessible(true);
Class<?> type = f.getType();
if (type.isArray()) {
int old = level;
level = 0;
Object obj = f.get(dest);
unpackArray(obj, bbuf);
level = old;
} else {
boolean handled = false;
for (Converter c : converters) {
if (c.handles(type)) {
c.toObject(f, dest, bbuf);
handled = true;
break;
}
}
// sub object unpacking
if (!handled) {
_unpack(f.get(dest), bbuf);
}
}
} catch (Exception e) {
System.err.println(e.getMessage());
}
// if true, ''should'' be settable
if (f.isAccessible() != isa)
f.setAccessible(isa);
}
--level;
return dest;
}
private void unpackArray(Object dest, ByteBuffer bbuf) {
String classname = dest.getClass().getName();
int size = Array.getLength(dest);
// We have a lowest level array!
if (classname.charAt(1) != '[') {
// For each element in the array add that to the list
for (int i = 0; i < size; ++i) {
Object obj = Array.get(dest, i);
Object tmp = _unpack(obj, bbuf);
Array.set(dest, i, tmp);
}
} else {
Object[] tmp = (Object[]) dest;
// Otherwise we have to determine the number of sub levels
// and call this function for each one
for (int i = 0; i < size; ++i) {
unpackArray(tmp[i], bbuf);
}
}
}
/**
* Unpacks the given src byte array into the given object.
*
* @param dest
* @param src
*/
public Object unpack(Object dest, byte[] src) {
level = 0;
ByteBuffer bbuf = ByteBuffer.wrap(src);
bbuf.order(ByteOrder.LITTLE_ENDIAN);
return _unpack(dest, bbuf);
}
/**
* Packs the given object into a byte array.
*/
public byte[] pack(Object src) throws IOException {
level = 0;
return _pack(src);
}
/**
* Packs the given object into a byte array.
*/
private byte[] _pack(Object src) throws IOException {
if (src == null) {
return new byte[0];
}
if (level == 0) {
Class<?> type = src.getClass();
try {
for (Converter c : converters) {
if (c.handles(type)) {
return c.toByteArray(null, src);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
ByteArrayOutputStream buf = new ByteArrayOutputStream();
if (src.getClass().isArray()) {
return packArray(src);
} else {
++level;
Field[] fld = null;
if (doSecure) {
fld = src.getClass().getDeclaredFields();
} else {
// Note: Safer (public only)
fld = src.getClass().getFields();
}
for (Field f : fld) {
boolean isAccessible = f.isAccessible();
try {
if (doSecure) {
f.setAccessible(true);
}
Class<?> type = f.getType();
if (type.isArray()) {
int old = level;
level = 0;
buf.write(packArray(f.get(src)));
level = old;
} else {
boolean handled = false;
for (Converter c : converters) {
if (c.handles(type)) {
buf.write(c.toByteArray(f, src));
handled = true;
break;
}
}
// sub object packing
if (!handled) {
buf.write(_pack(f.get(src)));
}
}
} catch (Exception e) {
System.err.println(e.getMessage());
}
// if true, ''should'' be settable
if (f.isAccessible() != isAccessible)
f.setAccessible(isAccessible);
}
--level;
}
return buf.toByteArray();
}
/**
* uhm, multi-dimensional... yeah. Recursive of course.
*
* @throws IOException
*/
private byte[] packArray(Object src) throws IOException {
String classname = src.getClass().getName();
int size = Array.getLength(src);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
// We have a lowest level array!
if (classname.charAt(1) != '[') {
// For each element in the array add that to the list
for (int i = 0; i < size; ++i) {
Object obj = Array.get(src, i);
// buf.put(_pack(obj));
buf.write(_pack(obj));
}
} else {
Object[] tmp = (Object[]) src;
// Otherwise we have to determine the number of sub levels
// and call this function for each one
for (int i = 0; i < size; ++i) {
buf.write(packArray(tmp[i]));
}
}
return buf.toByteArray();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment