/*
* This file is part of the X10 project (http://x10-lang.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* (C) Copyright IBM Corporation 2006-2012.
*/
package x10.serialization;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.TypeVariable;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import sun.misc.Unsafe;
import x10.io.SerialData;
import x10.rtt.NamedStructType;
import x10.rtt.NamedType;
import x10.rtt.RuntimeType;
import x10.runtime.impl.java.Runtime;
/**
* An instance of a DeserializerThunk knows how to deserialize a specific class.
* The various subclasses of this abstract class encode different cases of
* the serialization protocol and are matched 1:1 with subclasses of SerializerThunk.<p>
*
* The motivation for this software architecture is to improve the runtime efficiency
* of serialization/deserialization by doing as much of the protocol and meta-data
* operations once (at Thunk creation time) and amortizing that cost across all the
* uses of the Thunk object.
*
* @see SerializerThunk
*/
abstract class DeserializerThunk {
protected static final String CONSTRUCTOR_METHOD_NAME_FOR_REFLECTION = "$initForReflection";
protected static Unsafe unsafe = DeserializerThunk.getUnsafe();
protected static ConcurrentHashMap<Class<?>, DeserializerThunk> thunks = new ConcurrentHashMap<Class<?>, DeserializerThunk>(50);
protected final DeserializerThunk superThunk;
protected DeserializerThunk(DeserializerThunk st) {
superThunk = st;
}
/**
* Factory method to find or create the DeserializerThunk instance for clazz.
*
* @param clazz The class to be deserialized.
* @return The DeserializerThunk instance for the argument class.
*/
static DeserializerThunk getDeserializerThunk(Class<? extends Object> clazz) throws SecurityException, NoSuchFieldException, NoSuchMethodException {
DeserializerThunk ans = DeserializerThunk.thunks.get(clazz);
if (ans == null) {
ans = DeserializerThunk.getDeserializerThunkHelper(clazz);
DeserializerThunk.thunks.put(clazz, ans);
if (Runtime.TRACE_SER) {
Runtime.printTraceMessage("Creating deserialization thunk "+ans.getClass()+" for "+clazz);
}
}
return ans;
}
/**
* Create an instance of clazz and initialize it by reading
* values for its instance fields from jds.
*
* @param clazz The class to create
* @param jds The deserializer from which to obtain the new object's state.
* @return the deserialized object.
*/
@SuppressWarnings("unchecked")
<T> T deserializeObject(Class<?> clazz, X10JavaDeserializer jds) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, InstantiationException {
T obj = null;
// If the class is java.lang.Class we cannot create an instance in the following manner so we just skip it
if (!"java.lang.Class".equals(clazz.getName())) {
try {
assert !Modifier.isAbstract(clazz.getModifiers());
obj = (T)DeserializerThunk.unsafe.allocateInstance(clazz);
} catch (InstantiationException e) {
throw new RuntimeException(e);
}
}
int i = jds.record_reference(obj);
return deserializeObject(clazz, obj, i, jds);
}
/**
* Initialize obj, an instance of clazz, by reading its instance state from jds.
*
* @param clazz The class that is being deserialized
* @param obj The object instance of the class to initialize
* @param i The id of this object (in case deserialization needs to replace it)
* @param jds The deserializer from which to obtain the new object's state.
* @return The deserialized object.
*/
<T> T deserializeObject(Class<?> clazz, T obj, int i, X10JavaDeserializer jds) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, InstantiationException {
if (superThunk != null) {
obj = superThunk.deserializeObject(clazz.getSuperclass(), obj, i, jds);
}
return deserializeBody(clazz, obj, i, jds);
}
protected abstract <T> T deserializeBody(Class<?> clazz, T obj, int i, X10JavaDeserializer jds) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, InstantiationException;
protected static Unsafe getUnsafe() {
Unsafe unsafe = null;
try {
Class<Unsafe> uc = Unsafe.class;
Field[] fields = uc.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
if (fields[i].getName().equals("theUnsafe")) {
fields[i].setAccessible(true);
unsafe = (Unsafe) fields[i].get(uc);
break;
}
}
} catch (Exception ignore) {
}
return unsafe;
}
private static DeserializerThunk getDeserializerThunkHelper(Class<?> clazz) throws SecurityException, NoSuchFieldException, NoSuchMethodException {
// We need to handle these classes in a special way cause there implementation of serialization/deserialization is
// not straight forward. Hence we just call into the custom serialization of these classes.
if ("java.lang.String".equals(clazz.getName())) {
return new JavaLangStringDeserializerThunk(null);
} else if ("x10.rtt.NamedType".equals(clazz.getName())) {
return new SpecialCaseDeserializerThunk(null);
} else if ("x10.rtt.NamedStructType".equals(clazz.getName())) {
return new SpecialCaseDeserializerThunk(null);
} else if ("x10.rtt.RuntimeType".equals(clazz.getName())) {
return new SpecialCaseDeserializerThunk(null);
} else if ("x10.core.IndexedMemoryChunk".equals(clazz.getName())) {
return new SpecialCaseDeserializerThunk(null);
} else if ("x10.core.IndexedMemoryChunk$$Closure$0".equals(clazz.getName())) {
return new SpecialCaseDeserializerThunk(null);
} else if ("x10.core.IndexedMemoryChunk$$Closure$1".equals(clazz.getName())) {
return new SpecialCaseDeserializerThunk(null);
} else if (x10.core.GlobalRef.class.getName().equals(clazz.getName())) {
return new SpecialCaseDeserializerThunk(null);
} else if ("java.lang.Throwable".equals(clazz.getName())) {
return new SpecialCaseDeserializerThunk(null);
} else if ("java.lang.Class".equals(clazz.getName())) {
return new SpecialCaseDeserializerThunk(null);
} else if ("java.lang.Object".equals(clazz.getName())) {
return new SpecialCaseDeserializerThunk(null);
}
Class<?>[] interfaces = clazz.getInterfaces();
boolean isCustomSerializable = false;
boolean isHadoopSerializable = false;
for (Class<?> aInterface : interfaces) {
if ("x10.io.CustomSerialization".equals(aInterface.getName())) {
isCustomSerializable = true;
break;
}
}
if (Runtime.implementsHadoopWritable(clazz)) {
isHadoopSerializable = true;
}
if (isCustomSerializable && isHadoopSerializable) {
throw new RuntimeException("deserializer: " + clazz + " implements both x10.io.CustomSerialization and org.apache.hadoop.io.Writable.");
}
if (isCustomSerializable) {
return new CustomDeserializerThunk(clazz);
}
if (isHadoopSerializable) {
return new HadoopDeserializerThunk(clazz);
}
Class<?> superclass = clazz.getSuperclass();
DeserializerThunk superThunk = null;
if (!("java.lang.Object".equals(superclass.getName()) || "x10.core.Ref".equals(superclass.getName()) || "x10.core.Struct".equals(superclass.getName()))) {
superThunk = getDeserializerThunk(superclass);
}
return new FieldBasedDeserializerThunk(clazz, superThunk);
}
private static class FieldBasedDeserializerThunk extends DeserializerThunk {
protected final Field[] fields;
FieldBasedDeserializerThunk(Class<? extends Object> clazz, DeserializerThunk st) {
super(st);
// XTENLANG-2982,2983 transient fields may be initialized with readObject method.
Method readObjectMethod = null;
try {
readObjectMethod = clazz.getDeclaredMethod("readObject", java.io.ObjectInputStream.class);
} catch (Exception e) {}
// Sort the fields to get JVM-independent ordering.
Set<Field> flds = new TreeSet<Field>(new FieldComparator());
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
int modifiers = field.getModifiers();
if (Modifier.isStatic(modifiers) || (Modifier.isTransient(modifiers) && readObjectMethod == null)) {
continue;
}
field.setAccessible(true);
flds.add(field);
}
fields = flds.toArray(new Field[flds.size()]);
}
protected <T> T deserializeBody(Class<?> clazz, T obj, int i, X10JavaDeserializer jds) throws IOException, IllegalAccessException {
for (Field field : fields) {
Class<?> type = field.getType();
if (type.isPrimitive()) {
jds.readPrimitiveUsingReflection(field, obj);
} else if (type.isArray()) {
field.set(obj, jds.readArrayUsingReflection(type.getComponentType()));
} else if ("java.lang.String".equals(type.getName())) {
field.set(obj, jds.readStringUsingReflection());
} else {
Object value = jds.readRefUsingReflection();
field.set(obj, value);
}
}
return obj;
}
}
private static class HadoopDeserializerThunk extends DeserializerThunk {
private static final Class<?>[] EMPTY_ARRAY = new Class[]{};
protected final Constructor<?> constructor;
protected final Method readMethod;
HadoopDeserializerThunk(Class<? extends Object> clazz) throws SecurityException, NoSuchMethodException {
super(null);
constructor = clazz.getDeclaredConstructor(EMPTY_ARRAY);
constructor.setAccessible(true);
readMethod = clazz.getMethod("readFields", java.io.DataInput.class);
readMethod.setAccessible(true);
}
@SuppressWarnings("unchecked")
@Override
<T> T deserializeObject(Class<?> clazz, X10JavaDeserializer jds) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, InstantiationException {
if (Runtime.TRACE_SER) {
Runtime.printTraceMessage("Calling hadoop deserializer with object of type " + clazz);
}
// Hadoop assumes that the default constructor will be used to create the instance.
// The default constructor will execute field initialization expressions.
// So we have to mimic that behavior here.
T obj = (T)constructor.newInstance();
int i = jds.record_reference(obj);
return deserializeObject(clazz, obj, i, jds);
}
@Override
protected <T> T deserializeBody(Class<?> clazz, T obj, int i, X10JavaDeserializer jds) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
readMethod.invoke(obj, jds.in);
return obj;
}
}
private static class CustomDeserializerThunk extends DeserializerThunk {
protected final Field[] fields;
protected final Method makeMethod;
CustomDeserializerThunk(Class<? extends Object> clazz) throws SecurityException, NoSuchFieldException, NoSuchMethodException {
super(null);
// Even though this class implements a custom serialization protocol,
// the runtime needs to invisibly serialize those instance fields that
// are used to provide RTT information for generic types (not visible at the user-level).
TypeVariable<? extends Class<? extends Object>>[] typeParameters = clazz.getTypeParameters();
if (typeParameters.length > 0) {
// Must sort the fields to get JVM-independent ordering.
Set<Field> flds = new TreeSet<Field>(new FieldComparator());
for (TypeVariable<? extends Class<? extends Object>> typeParameter: typeParameters) {
Field field = clazz.getDeclaredField(typeParameter.getName());
field.setAccessible(true);
flds.add(field);
}
fields = flds.toArray(new Field[flds.size()]);
} else {
fields = new Field[0];
}
// We can't use the same method name in all classes cause it creates an endless loop cause when super.init is called it calls back to this method
makeMethod = clazz.getMethod(clazz.getName().replace(".", "$") + "$" + DeserializerThunk.CONSTRUCTOR_METHOD_NAME_FOR_REFLECTION, SerialData.class);
makeMethod.setAccessible(true);
}
@Override
protected <T> T deserializeBody(Class<?> clazz, T obj, int i, X10JavaDeserializer jds) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
for (Field field : fields) {
Object value = jds.readRefUsingReflection();
field.set(obj, value);
}
SerialData serialData = (SerialData) jds.readRefUsingReflection();
makeMethod.invoke(obj, serialData);
return obj;
}
}
private static class JavaLangStringDeserializerThunk extends DeserializerThunk {
JavaLangStringDeserializerThunk(Class <? extends Object> clazz) {
super(null);
}
@Override
<T> T deserializeObject(Class<?> clazz, X10JavaDeserializer jds) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, InstantiationException {
int i = jds.record_reference(null);
return deserializeObject(clazz, null, i, jds);
}
@Override
<T> T deserializeObject(Class<?> clazz, T obj, int i, X10JavaDeserializer jds) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, InstantiationException {
return deserializeBody(clazz, obj, i, jds);
}
@SuppressWarnings("unchecked")
protected <T> T deserializeBody(Class<?> clazz, T obj, int i, X10JavaDeserializer jds) throws IOException {
String realVal = jds.readStringValue();
jds.update_reference(i, realVal);
return (T) realVal;
}
}
private static class SpecialCaseDeserializerThunk extends DeserializerThunk {
SpecialCaseDeserializerThunk(Class <? extends Object> clazz) {
super(null);
}
SpecialCaseDeserializerThunk(Class <? extends Object> clazz, DeserializerThunk st) {
super(st);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
protected <T> T deserializeBody(Class<?> clazz, T obj, int i, X10JavaDeserializer jds) throws IOException {
if ("x10.rtt.NamedType".equals(clazz.getName())) {
NamedType.$_deserialize_body((NamedType) obj, jds);
return obj;
} else if ("x10.rtt.NamedStructType".equals(clazz.getName())) {
NamedStructType.$_deserialize_body((NamedStructType) obj, jds);
return obj;
} else if ("x10.rtt.RuntimeType".equals(clazz.getName())) {
X10JavaSerializable x10JavaSerializable = RuntimeType.$_deserialize_body((RuntimeType) obj, jds);
if (obj != x10JavaSerializable) {
jds.update_reference(i, x10JavaSerializable);
obj = (T) x10JavaSerializable;
}
return obj;
} else if ("x10.core.IndexedMemoryChunk".equals(clazz.getName())) {
x10.core.IndexedMemoryChunk imc = (x10.core.IndexedMemoryChunk) obj;
x10.core.IndexedMemoryChunk.$_deserialize_body(imc, jds);
return (T) imc;
} else if ("x10.core.IndexedMemoryChunk$$Closure$0".equals(clazz.getName())) {
return (T) x10.core.IndexedMemoryChunk.$Closure$0.$_deserialize_body((x10.core.IndexedMemoryChunk.$Closure$0) obj, jds);
} else if ("x10.core.IndexedMemoryChunk$$Closure$1".equals(clazz.getName())) {
return (T) x10.core.IndexedMemoryChunk.$Closure$1.$_deserialize_body((x10.core.IndexedMemoryChunk.$Closure$1) obj, jds);
} else if (x10.core.GlobalRef.class.getName().equals(clazz.getName())) {
return (T) x10.core.GlobalRef.$_deserialize_body((x10.core.GlobalRef) obj, jds);
} else if ("java.lang.Throwable".equals(clazz.getName())) {
if (X10JavaSerializer.THROWABLES_SERIALIZE_MESSAGE) {
try {
String message = (String)jds.readRef();
Field detailMessageField = java.lang.Throwable.class.getDeclaredField("detailMessage");
detailMessageField.setAccessible(true);
detailMessageField.set(obj, message);
} catch (Exception e) {
e.printStackTrace();
}
}
if (X10JavaSerializer.THROWABLES_SERIALIZE_STACKTRACE) {
java.lang.StackTraceElement[] trace = (java.lang.StackTraceElement[]) jds.readArrayUsingReflection(java.lang.StackTraceElement.class);
java.lang.Throwable t = (java.lang.Throwable) obj;
t.setStackTrace(trace);
}
if (X10JavaSerializer.THROWABLES_SERIALIZE_CAUSE) {
try {
java.lang.Throwable cause = (java.lang.Throwable) jds.readRef();
Field causeField = java.lang.Throwable.class.getDeclaredField("cause");
causeField.setAccessible(true);
causeField.set(obj, cause);
} catch (Exception e) {
e.printStackTrace();
}
}
return obj;
} else if ("java.lang.Class".equals(clazz.getName())) {
String className = jds.readString();
try {
T t = (T) Class.forName(className);
jds.update_reference(i, t);
return t;
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
} else if ("java.lang.Object".equals(clazz.getName())) {
return obj;
}
throw new RuntimeException("Unhandled type in special case thunk: "+obj.getClass());
}
}
}