Created
March 19, 2012 10:16
-
-
Save Chase-san/2106501 to your computer and use it in GitHub Desktop.
The Pack Class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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