/*
* Copyright (c) 2006 Leonardo "Bubble" Mesquita
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.bubble.serializer;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtNewMethod;
import javassist.NotFoundException;
import javassist.compiler.Javac.CtFieldWithInit;
import com.bubble.utils.StringUtils;
/**
* Generates customized code for serialization.
* This is where the magic happens.
*
* <p>The generator inspects classes with reflection to generate
* code that performs serialization of fields in the most direct way possible.
* For non-private fields, the generated code will access fields directly, not
* through reflection.
*
* @author Leonardo "Bubble" Mesquita
*
*/
public class Generator {
private static final Map serializers = new HashMap();
private static final Map deserializers = new HashMap();
private static final String OBJECT_PARAM = "obj";
private static final String BUFFER_VAR = "buffer";
private static final String OBJECT_VAR = "object";
private static final String CONTEXT_VAR = "context";
private static final String FIELD_SUFFIX = "_field";
private static final String OBJECT_PARAM_DECL = Object.class.getName()+" "+OBJECT_PARAM;
private static final String BUFFER_VAR_DECL = ByteBuffer.class.getName()+" "+BUFFER_VAR;
private static final String SERIALIZATION_CONTEXT_VAR_DECL = LightSerializationContext.class.getName()+" "+CONTEXT_VAR;
private static final String DESERIALIZATION_CONTEXT_VAR_DECL = LightDeserializationContext.class.getName()+" "+CONTEXT_VAR;
public Serializer getSerializer(Class clazz) {
Serializer result = (Serializer)serializers.get(clazz);
if(result == null) {
result = generateSerializer(clazz);
serializers.put(clazz, result);
}
return result;
}
public static void preloadSerializer(Class clazz) {
new Generator().getSerializer(clazz);
}
public static void preloadDeserializer(Class clazz) {
new Generator().getDeserializer(clazz);
}
private static class FieldComparator implements Comparator {
public int compare(Object obj1, Object obj2) {
Field f1 = (Field)obj1;
Field f2 = (Field)obj2;
return f1.getName().compareTo(f2.getName());
}
}
private static final FieldComparator fieldComparator = new FieldComparator();
private CtClass createSerializationClass(String className, boolean deserializer) {
ClassPool cp = ClassPool.getDefault();
CtClass cl = cp.makeClass(className+"$__"+(deserializer?"DE":"")+"SERIALIZER__");
String interfaceName = (deserializer?Deserializer.class.getName():Serializer.class.getName());
try {
cl.addInterface(cp.get(interfaceName));
} catch (NotFoundException e) {
throw new NoClassDefFoundError("Could not find Serializer interface");
}
return cl;
}
private void setSuperclass(CtClass cl, Class superClass, boolean deserializer) {
ClassPool cp = ClassPool.getDefault();
Class sp = (deserializer?(Object)getDeserializer(superClass):(Object)getSerializer(superClass)).getClass();
try {
cl.setSuperclass(cp.get(sp.getName()));
} catch (NotFoundException e) {
throw new NoClassDefFoundError("Could not find Serializer interface");
} catch (CannotCompileException e) {
throw new GeneratorRuntimeException("Could not generate serializer for subclass of "+superClass.getName(), e);
}
}
private void addMethod(String body, CtClass cl, String className) {
try {
// System.out.println(body);
cl.addMethod(CtNewMethod.make(body, cl));
} catch (CannotCompileException e) {
throw new GeneratorRuntimeException("Could not generate serializer for class "+className, e);
}
}
private Object createInstance(CtClass cl, String className) {
try {
Object result = cl.toClass(this.getClass().getClassLoader(), null).newInstance();
return result;
} catch (InstantiationException e) {
throw new GeneratorRuntimeException("Could not generate serializer for class "+className, e);
} catch (IllegalAccessException e) {
throw new GeneratorRuntimeException("Could not generate serializer for class "+className, e);
} catch (CannotCompileException e) {
throw new GeneratorRuntimeException("Could not generate serializer for class "+className, e);
}
}
private String generateSerializeInstanceInfoMethodHeader(String className) {
String body = "public void serializeInstanceInfo("
+ OBJECT_PARAM_DECL+", "
+ BUFFER_VAR_DECL+", "
+ SERIALIZATION_CONTEXT_VAR_DECL+") {\n";
return body;
}
private String generateObjectCast(String className) {
return "\t"+className+ " "+OBJECT_VAR +" = ("+className+") "+OBJECT_PARAM+";\n";
}
private String generateSerializeMethodHeader(String className) {
String body = "public void serialize("
+ OBJECT_PARAM_DECL+", "
+ BUFFER_VAR_DECL+", "
+ SERIALIZATION_CONTEXT_VAR_DECL+") {\n";
return body;
}
private String generateInstantiateMethodHeader(String className) {
String body = "public "+Object.class.getName()+" instantiate("
+ BUFFER_VAR_DECL+", "
+ DESERIALIZATION_CONTEXT_VAR_DECL+") {\n";
return body;
}
private String generateDeserializeMethodHeader(String className) {
String body = "public void deserialize("
+ OBJECT_PARAM_DECL+", "
+ BUFFER_VAR_DECL+", "
+ DESERIALIZATION_CONTEXT_VAR_DECL+") {\n";
return body;
}
private Serializer generateSerializer(Class clazz) {
if(clazz.isArray()) {
return generateArraySerializer(clazz);
}
if (String.class.equals(clazz)) {
return StringSerializer.getInstance();
}
if(!Serializable.class.isAssignableFrom(clazz)) {
throw new IllegalArgumentException("Class "+clazz.getName()+ " is not Serializable");
}
boolean hasSerialSuper = Serializable.class.isAssignableFrom(clazz.getSuperclass());
String className = clazz.getName();
boolean onlyPublic = className.startsWith("java");
String fullName = className;
if(onlyPublic) {
fullName="com.bubble."+fullName;
}
CtClass cl = createSerializationClass(fullName, false);
if(hasSerialSuper) {
setSuperclass(cl, clazz.getSuperclass(), false);
}
Field[] fields = clazz.getDeclaredFields();
// Sorts fields alphabetically by name
Arrays.sort(fields, fieldComparator);
generateUnaccessibleFieldAccessors(cl, clazz, fields, onlyPublic);
// Creates an empty "serializeInstanceInfo" method
String body = generateSerializeInstanceInfoMethodHeader(className);
body += "}\n";
addMethod(body, cl, className);
body = generateSerializeMethodHeader(className);
if(hasSerialSuper) {
body += "\tsuper.serialize("+OBJECT_PARAM+", "+BUFFER_VAR+ ", "+CONTEXT_VAR+");\n";
}
body += generateObjectCast(className);
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
body += generateFieldSerialization(field, onlyPublic);
}
body += "}\n";
addMethod(body, cl, className);
return (Serializer)createInstance(cl, className);
}
private String getCastName(Class clazz) {
if(!clazz.isArray()) {
return clazz.getName();
}
Class baseType = clazz.getComponentType();
int depth = 1;
while(baseType.isArray()) {
baseType = baseType.getComponentType();
depth++;
}
String className = baseType.getName();
for (int i = depth; i > 0; i--) {
className += "[]";
}
return className;
}
private Serializer generateArraySerializer(Class clazz) {
String arrayName = clazz.getName();
String className = getCastName(clazz);
String clname = arrayName.replaceAll("\\[", "_ARR\\$").replaceAll(";", "_");
CtClass cl = createSerializationClass(clname, false);
// Creates a "serializeInstanceInfo" method
String body = generateSerializeInstanceInfoMethodHeader(className);
body += generateObjectCast(className);
body += "\t"+BUFFER_VAR+".putInt("+OBJECT_VAR+".length);\n";
body += "}\n";
addMethod(body, cl, className);
body = generateSerializeMethodHeader(className);
body += generateObjectCast(className);
body += "\tfor(int i = 0; i < "+OBJECT_VAR+".length; i++) {\n";
body += generateElementSerialization(OBJECT_VAR+"[i]", clazz.getComponentType(), "\t\t");
body += "\t}\n";
body += "}\n";
addMethod(body, cl, className);
return (Serializer)createInstance(cl, className);
}
private static final int bannedMods = Modifier.STATIC | Modifier.TRANSIENT | Modifier.FINAL;
private String generateFieldSerialization(Field field, boolean onlyPublic) {
int modifiers = field.getModifiers();
if((modifiers & bannedMods) != 0) {
return "";
}
if(Modifier.isPrivate(modifiers) || (onlyPublic && !Modifier.isPublic(modifiers))) {
return generateUnaccessibleElementSerialization(field.getName(), field.getType(), "\t");
} else {
return generateElementSerialization(OBJECT_VAR+"."+field.getName(), field.getType(), "\t");
}
}
private void generateUnaccessibleFieldAccessors(CtClass cl, Class clazz, Field[] fields, boolean onlyPublic) {
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
int modifiers = field.getModifiers();
if(!(Modifier.isPrivate(modifiers) || (onlyPublic && !Modifier.isPublic(modifiers)))) {
continue;
}
String decl = "private "+Field.class.getName()+" "+field.getName()+FIELD_SUFFIX+" = "
+FieldResolver.class.getName()+".getField("+clazz.getName()+".class, \""+field.getName()+"\");";
CtField cf;
try {
cf = CtFieldWithInit.make(decl, cl);
cl.addField(cf);
} catch (CannotCompileException e) {
throw new GeneratorRuntimeException("Could not generate private field accessor for "+field.toString(), e);
}
}
}
private String generateUnaccessibleElementSerialization(String fieldName, Class type, String tabs) {
String accessParam = "("+fieldName+FIELD_SUFFIX+", "+OBJECT_VAR+")";
if(boolean.class.equals(type)) {
String access = FieldResolver.class.getName()+".getBoolean"
+accessParam;
return tabs+BUFFER_VAR+".put(("+access+")?(byte)1:(byte)0);\n";
}
if (byte.class.equals(type)) {
String access = FieldResolver.class.getName()+".getByte"
+accessParam;
return tabs+BUFFER_VAR+".put("+access+");\n";
}
if (short.class.equals(type)) {
String access = FieldResolver.class.getName()+".getShort"
+accessParam;
return tabs+BUFFER_VAR+".putShort("+access+");\n";
}
if (int.class.equals(type)) {
String access = FieldResolver.class.getName()+".getInt"
+accessParam;
return tabs+BUFFER_VAR+".putInt("+access+");\n";
}
if (long.class.equals(type)) {
String access = FieldResolver.class.getName()+".getLong"
+accessParam;
return tabs+BUFFER_VAR+".putLong("+access+");\n";
}
if (float.class.equals(type)) {
String access = FieldResolver.class.getName()+".getFloat"
+accessParam;
return tabs+BUFFER_VAR+".putFloat("+access+");\n";
}
if (double.class.equals(type)) {
String access = FieldResolver.class.getName()+".getDouble"
+accessParam;
return tabs+BUFFER_VAR+".putDouble("+access+");\n";
}
if (char.class.equals(type)) {
String access = FieldResolver.class.getName()+".getChar"
+accessParam;
return tabs+BUFFER_VAR+".putChar("+access+");\n";
}
String access = FieldResolver.class.getName()+".get"
+accessParam;
return tabs+CONTEXT_VAR+".serialize("+access+", "+BUFFER_VAR+");\n";
}
private String generateElementSerialization(String name, Class type, String tabs) {
if(boolean.class.equals(type)) {
return tabs+BUFFER_VAR+".put(("+name+")?(byte)1:(byte)0);\n";
}
if (byte.class.equals(type)) {
return tabs+BUFFER_VAR+".put("+name+");\n";
}
if (short.class.equals(type)) {
return tabs+BUFFER_VAR+".putShort("+name+");\n";
}
if (int.class.equals(type)) {
return tabs+BUFFER_VAR+".putInt("+name+");\n";
}
if (long.class.equals(type)) {
return tabs+BUFFER_VAR+".putLong("+name+");\n";
}
if (float.class.equals(type)) {
return tabs+BUFFER_VAR+".putFloat("+name+");\n";
}
if (double.class.equals(type)) {
return tabs+BUFFER_VAR+".putDouble("+name+");\n";
}
if (char.class.equals(type)) {
return tabs+BUFFER_VAR+".putChar("+name+");\n";
}
return tabs+CONTEXT_VAR+".serialize("+name+", "+BUFFER_VAR+");\n";
}
public Deserializer getDeserializer(String className) throws ClassNotFoundException {
return getDeserializer(Class.forName(className));
}
public Deserializer getDeserializer(Class clazz) {
Deserializer result = (Deserializer)deserializers.get(clazz);
if(result == null) {
result = generateDeserializer(clazz);
deserializers.put(clazz, result);
}
return result;
}
private Deserializer generateDeserializer(Class clazz) {
if(clazz.isArray()) {
return generateArrayDeserializer(clazz);
}
if(String.class.equals(clazz)) {
return StringDeserializer.getInstance();
}
if(!Serializable.class.isAssignableFrom(clazz)) {
throw new IllegalArgumentException("Class "+clazz.getName()+ " is not Serializable");
}
boolean hasSerialSuper = Serializable.class.isAssignableFrom(clazz.getSuperclass());
String className = clazz.getName();
boolean onlyPublic = className.startsWith("java");
String fullName = className;
if(onlyPublic) {
fullName="com.bubble."+fullName;
}
CtClass cl = createSerializationClass(fullName, true);
if(hasSerialSuper) {
setSuperclass(cl, clazz.getSuperclass(), true);
}
Field[] fields = clazz.getDeclaredFields();
// Sorts fields alphabetically by name
Arrays.sort(fields, fieldComparator);
generateUnaccessibleFieldAccessors(cl, clazz, fields, onlyPublic);
String body = generateInstantiateMethodHeader(className);
body += "\treturn new "+className+"();\n";
body += "}\n";
addMethod(body, cl, className);
body = generateDeserializeMethodHeader(className);
if(hasSerialSuper) {
body += "\tsuper.deserialize("+OBJECT_PARAM+", "+BUFFER_VAR+", "+CONTEXT_VAR+");\n";
}
body += generateObjectCast(className);
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
body += generateFieldDeserialization(field, onlyPublic);
}
body += "}\n";
addMethod(body, cl, className);
return (Deserializer)createInstance(cl, className);
}
private String generateFieldDeserialization(Field field, boolean onlyPublic) {
int modifiers = field.getModifiers();
if((modifiers & bannedMods) != 0) {
return "";
}
if(Modifier.isPrivate(modifiers) || (onlyPublic && !Modifier.isPublic(modifiers))) {
return generatePrivateElementDeserialization(field.getName(), field.getType(), "\t");
} else {
return generateElementDeserialization(OBJECT_VAR+"."+field.getName(), field.getType(), "\t");
}
}
private String generatePrivateElementDeserialization(String fieldName, Class type, String tabs) {
String prefix = FieldResolver.class.getName()+".";
String method;
String accessParam = "("+fieldName+FIELD_SUFFIX+", "+OBJECT_VAR+", ";
String value;
String suffix = ");\n";
if(boolean.class.equals(type)) {
value = "("+BUFFER_VAR+".get() != 0)";
method = "setBoolean";
}
else if (byte.class.equals(type)) {
value = BUFFER_VAR+".get()";
method = "setByte";
}
else if (short.class.equals(type)) {
value = BUFFER_VAR+".getShort()";
method = "setShort";
}
else if (int.class.equals(type)) {
value = BUFFER_VAR+".getInt()";
method = "setInt";
}
else if (long.class.equals(type)) {
value = BUFFER_VAR+".getLong()";
method = "setLong";
}
else if (float.class.equals(type)) {
value = BUFFER_VAR+".getFloat()";
method = "setFloat";
}
else if (double.class.equals(type)) {
value = BUFFER_VAR+".getDouble()";
method = "setDouble";
}
else if (char.class.equals(type)) {
value = BUFFER_VAR+".getChar()";
method = "setChar";
}
else {
value = CONTEXT_VAR+".deserialize("+BUFFER_VAR+")";
method = "set";
}
return tabs+prefix+method+accessParam+value+suffix;
}
private String generateElementDeserialization(String name, Class type, String tabs) {
if(boolean.class.equals(type)) {
return tabs+name+" = ("+BUFFER_VAR+".get() != 0);\n";
}
if (byte.class.equals(type)) {
return tabs+name+" = "+BUFFER_VAR+".get();\n";
}
if (short.class.equals(type)) {
return tabs+name+" = "+BUFFER_VAR+".getShort();\n";
}
if (int.class.equals(type)) {
return tabs+name+" = "+BUFFER_VAR+".getInt();\n";
}
if (long.class.equals(type)) {
return tabs+name+" = "+BUFFER_VAR+".getLong();\n";
}
if (float.class.equals(type)) {
return tabs+name+" = "+BUFFER_VAR+".getFloat();\n";
}
if (double.class.equals(type)) {
return tabs+name+" = "+BUFFER_VAR+".getDouble();\n";
}
if (char.class.equals(type)) {
return tabs+name+" = "+BUFFER_VAR+".getChar();\n";
}
return tabs+name+" = ("+getCastName(type)+") "+CONTEXT_VAR+".deserialize("+BUFFER_VAR+");\n";
}
private Deserializer generateArrayDeserializer(Class clazz) {
String arrayName = clazz.getName();
Class baseType = clazz.getComponentType();
int depth = 0;
while(baseType.isArray()) {
baseType = baseType.getComponentType();
depth++;
}
String className = baseType.getName()+"[]";
String newOperation = baseType.getName()+"[len]";
for (int i = depth; i > 0; i--) {
className += "[]";
newOperation += "[]";
}
String clname = arrayName.replaceAll("\\[", "_ARR\\$").replaceAll(";", "_");;
CtClass cl = createSerializationClass(clname, true);
String body = generateInstantiateMethodHeader(className);
body += "\tint len = "+BUFFER_VAR+".getInt();\n";
body += "\treturn new "+newOperation+";\n";
body += "}\n";
addMethod(body, cl, className);
body = generateDeserializeMethodHeader(className);
body += generateObjectCast(className);
body += "\tfor(int i = 0; i < "+OBJECT_VAR+".length; i++) {\n";
body += generateElementDeserialization(OBJECT_VAR+"[i]", clazz.getComponentType(), "\t\t");
body += "\t}\n";
body += "}\n";
addMethod(body, cl, className);
return (Deserializer)createInstance(cl, className);
}
private static class StringSerializer implements Serializer {
private StringSerializer() {
}
public static StringSerializer getInstance() {
return new StringSerializer();
}
public void serializeInstanceInfo(Object obj, ByteBuffer buffer, LightSerializationContext context) {
String str = (String)obj;
StringUtils.serialize(str, buffer);
}
public void serialize(Object obj, ByteBuffer buffer, LightSerializationContext context) {
// Strings have no state data, since they're immutable
}
}
private static class StringDeserializer implements Deserializer {
private StringDeserializer() {
}
public static StringDeserializer getInstance() {
return new StringDeserializer();
}
public Object instantiate(ByteBuffer buffer, LightDeserializationContext context) {
return StringUtils.deserialize(buffer);
}
public void deserialize(Object obj, ByteBuffer buffer, LightDeserializationContext context) {
// Strings have no state data, since they're immutable
}
}
}