/*
* 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.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 x10.io.CustomSerialization;
import x10.io.SerialData;
import x10.runtime.impl.java.Runtime;
/**
* An instance of a SerializerThunk knows how to serialize 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 DeserializerThunk.<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 DeserializerThunk
*/
abstract class SerializerThunk {
private static ConcurrentHashMap<Class<?>, SerializerThunk> thunks = new ConcurrentHashMap<Class<?>, SerializerThunk>(50);
protected final SerializerThunk superThunk;
protected SerializerThunk(SerializerThunk st) {
superThunk = st;
}
/**
* Factory method to find or create the SerializerThunk instance for clazz.
* @param clazz The class for which to find a SerializerThunk instance
* @return a SerializerThunk instance for clazz
*/
static SerializerThunk getSerializerThunk(Class<? extends Object> clazz) throws SecurityException, NoSuchFieldException, NoSuchMethodException {
SerializerThunk ans = thunks.get(clazz);
if (ans == null) {
ans = SerializerThunk.getSerializerThunkHelper(clazz);
thunks.put(clazz, ans);
if (Runtime.TRACE_SER) {
Runtime.printTraceMessage("Creating serialization thunk "+ans.getClass()+" for "+clazz);
}
}
return ans;
}
/**
* Serialize obj, an instance of clazz, into xjs.
*
* @param obj The object to serialize
* @param clazz The class of obj
* @param xjs The serializer into which the object should be serialized.
*/
<T> void serializeObject(T obj, Class<? extends Object > clazz, X10JavaSerializer xjs) throws IllegalAccessException, IOException, IllegalArgumentException, InvocationTargetException, SecurityException, NoSuchFieldException {
if (superThunk != null) {
superThunk.serializeObject(obj, clazz.getSuperclass(), xjs);
}
serializeBody(obj, clazz, xjs);
}
abstract <T> void serializeBody(T obj, Class<? extends Object> clazz, X10JavaSerializer xjs) throws IllegalAccessException, IOException, IllegalArgumentException, InvocationTargetException, SecurityException, NoSuchFieldException;
private static SerializerThunk getSerializerThunkHelper(Class<? extends Object> clazz) throws SecurityException, NoSuchFieldException, NoSuchMethodException {
// We need to handle these classes in a special way because their
// implementation of serialization/deserialization is not straight forward.
if ("java.lang.String".equals(clazz.getName())) {
return new SerializerThunk.JavaLangStringSerializerThunk(clazz);
} else if ("x10.rtt.NamedType".equals(clazz.getName())) {
SerializerThunk superThunk = getSerializerThunk(clazz.getSuperclass());
return new SerializerThunk.SpecialCaseSerializerThunk(clazz, superThunk);
} else if ("x10.rtt.NamedStructType".equals(clazz.getName())) {
SerializerThunk superThunk = getSerializerThunk(clazz.getSuperclass());
return new SerializerThunk.SpecialCaseSerializerThunk(clazz, superThunk);
} else if ("x10.rtt.RuntimeType".equals(clazz.getName())) {
return new SerializerThunk.SpecialCaseSerializerThunk(clazz);
} else if ("x10.core.IndexedMemoryChunk".equals(clazz.getName())) {
return new SerializerThunk.SpecialCaseSerializerThunk(clazz);
} else if ("x10.core.IndexedMemoryChunk$$Closure$0".equals(clazz.getName())) {
return new SerializerThunk.SpecialCaseSerializerThunk(clazz);
} else if ("x10.core.IndexedMemoryChunk$$Closure$1".equals(clazz.getName())) {
return new SerializerThunk.SpecialCaseSerializerThunk(clazz);
} else if (x10.core.GlobalRef.class.getName().equals(clazz.getName())) {
return new SerializerThunk.SpecialCaseSerializerThunk(clazz);
} else if ("java.lang.Throwable".equals(clazz.getName())) {
return new SerializerThunk.SpecialCaseSerializerThunk(clazz);
} else if ("java.lang.Class".equals(clazz.getName())) {
return new SerializerThunk.SpecialCaseSerializerThunk(clazz);
} else if ("java.lang.Object".equals(clazz.getName())) {
return new SerializerThunk.SpecialCaseSerializerThunk(clazz);
}
Class<?>[] interfaces = clazz.getInterfaces();
boolean isCustomSerializable = false;
boolean isHadoopSerializable = Runtime.implementsHadoopWritable(clazz);
boolean isX10JavaSerializable = x10.serialization.X10JavaSerializable.class.isAssignableFrom(clazz);
for (Class<?> aInterface : interfaces) {
if ("x10.io.CustomSerialization".equals(aInterface.getName())) {
isCustomSerializable = true;
break;
}
}
// Error checking. We don't support classes that try to implement both Hadoop and X10 serialization protocols
if (isHadoopSerializable) {
if (isCustomSerializable) {
throw new RuntimeException("serializer: " + clazz + " implements both x10.io.CustomSerialization and org.apache.hadoop.io.Writable.");
}
if (isX10JavaSerializable) {
throw new RuntimeException("serializer: " + clazz + " implements both x10.serialization.X10JavaSerializable and org.apache.hadoop.io.Writable.");
}
}
if (isCustomSerializable) {
return new SerializerThunk.CustomSerializerThunk(clazz);
}
if (isX10JavaSerializable) {
return new X10JavaSerializableSerializerThunk(clazz);
}
if (isHadoopSerializable) {
return new SerializerThunk.HadoopSerializerThunk(clazz);
}
// A vanilla Java class that doesn't implement a special protocol.
// We are going to serialize if via reflective access to its instance fields.
Class<?> superclass = clazz.getSuperclass();
SerializerThunk superThunk = null;
if (!("java.lang.Object".equals(superclass.getName()) || "x10.core.Ref".equals(superclass.getName()) || "x10.core.Struct".equals(superclass.getName()))) {
superThunk = getSerializerThunk(clazz.getSuperclass());
}
return new SerializerThunk.FieldBasedSerializerThunk(clazz, superThunk);
}
/**
* A thunk for a vanilla X10 class (supports compiler-generated serialization code).
*/
private static class X10JavaSerializableSerializerThunk extends SerializerThunk {
protected final Method serializeMethod;
public X10JavaSerializableSerializerThunk(Class<?> clazz) {
super(null); // The compiler-generated serialization code will invoke the superclass serializer directly
try {
serializeMethod = clazz.getMethod("$_serialize", X10JavaSerializer.class);
} catch (NoSuchMethodException e) {
System.err.println("SerializerThunk: class "+clazz+" does not have a $_serialize method");
throw new RuntimeException(e);
}
serializeMethod.setAccessible(true);
}
@Override
<T> void serializeBody(T obj, Class<? extends Object> clazz, X10JavaSerializer xjs) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
serializeMethod.invoke(obj, xjs);
}
}
private static class FieldBasedSerializerThunk extends SerializerThunk {
protected final Field[] fields;
FieldBasedSerializerThunk(Class<? extends Object> clazz, SerializerThunk 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()]);
}
<T> void serializeBody(T obj, Class<? extends Object> clazz, X10JavaSerializer xjs) throws IllegalAccessException, IOException {
for (Field field : fields) {
Class<?> type = field.getType();
if (type.isPrimitive()) {
xjs.writePrimitiveUsingReflection(field, obj);
} else if (type.isArray()) {
xjs.writeArrayUsingReflection(field.get(obj));
} else if ("java.lang.String".equals(type.getName())) {
xjs.writeStringUsingReflection(field, obj);
} else {
xjs.writeObjectUsingReflection(field.get(obj));
}
}
}
}
private static class HadoopSerializerThunk extends SerializerThunk {
protected final Method writeMethod;
HadoopSerializerThunk(Class<? extends Object> clazz) throws SecurityException, NoSuchMethodException {
super(null);
writeMethod = clazz.getMethod("write", java.io.DataOutput.class);
writeMethod.setAccessible(true);
}
<T> void serializeBody(T obj, Class<? extends Object> clazz, X10JavaSerializer xjs) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
if (Runtime.TRACE_SER) {
Runtime.printTraceMessage("\tInvoking "+writeMethod);
}
writeMethod.invoke(obj, xjs.out);
}
}
private static class CustomSerializerThunk extends SerializerThunk {
protected final Field[] fields;
CustomSerializerThunk(Class<? extends Object> clazz) throws SecurityException, NoSuchFieldException {
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];
}
}
<T> void serializeBody(T obj, Class<? extends Object> clazz, X10JavaSerializer xjs) throws IllegalArgumentException, IOException, IllegalAccessException {
for (Field field: fields) {
xjs.writeObjectUsingReflection(field.get(obj));
}
CustomSerialization cs = (CustomSerialization)obj;
SerialData serialData = cs.serialize();
xjs.writeObjectUsingReflection(serialData);
}
}
private static class JavaLangStringSerializerThunk extends SerializerThunk {
JavaLangStringSerializerThunk(Class <? extends Object> clazz) {
super(null);
}
<T> void serializeBody(T obj, Class<? extends Object> clazz, X10JavaSerializer xjs) throws IOException, SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
xjs.writeStringValue((String) obj);
}
}
private static class SpecialCaseSerializerThunk extends SerializerThunk {
SpecialCaseSerializerThunk(Class <? extends Object> clazz) {
super(null);
}
SpecialCaseSerializerThunk(Class <? extends Object> clazz, SerializerThunk st) {
super(st);
}
@SuppressWarnings("rawtypes")
<T> void serializeBody(T obj, Class<? extends Object> clazz, X10JavaSerializer xjs) throws IOException, SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
if ("x10.rtt.NamedType".equals(clazz.getName())) {
Field typeNameField = clazz.getDeclaredField("typeName");
String typeName = (String) typeNameField.get(obj);
xjs.write(typeName);
} else if ("x10.rtt.NamedStructType".equals(clazz.getName())) {
Field typeNameField = clazz.getDeclaredField("typeName");
String typeName = (String) typeNameField.get(obj);
xjs.write(typeName);
} else if ("x10.rtt.RuntimeType".equals(clazz.getName())) {
Field javaClassField = clazz.getDeclaredField("javaClass");
Class<?> javaClass = (Class<?>) javaClassField.get(obj);
short sid = xjs.getSerializationId(javaClass, null);
xjs.write(sid);
} else if ("x10.core.IndexedMemoryChunk".equals(clazz.getName())) {
((x10.core.IndexedMemoryChunk) obj).$_serialize(xjs);
} else if ("x10.core.IndexedMemoryChunk$$Closure$0".equals(clazz.getName())) {
((x10.core.IndexedMemoryChunk.$Closure$0) obj).$_serialize(xjs);
} else if ("x10.core.IndexedMemoryChunk$$Closure$1".equals(clazz.getName())) {
((x10.core.IndexedMemoryChunk.$Closure$1) obj).$_serialize(xjs);
} else if (x10.core.GlobalRef.class.getName().equals(clazz.getName())) {
((x10.core.GlobalRef) obj).$_serialize(xjs);
} else if ("java.lang.Throwable".equals(clazz.getName())) {
java.lang.Throwable t = (java.lang.Throwable) obj;
if (X10JavaSerializer.THROWABLES_SERIALIZE_MESSAGE) {
xjs.write(t.getMessage());
}
if (X10JavaSerializer.THROWABLES_SERIALIZE_STACKTRACE) {
xjs.writeArrayUsingReflection(t.getStackTrace());
}
if (X10JavaSerializer.THROWABLES_SERIALIZE_CAUSE) {
xjs.write(t.getCause());
}
} else if ("java.lang.Class".equals(clazz.getName())) {
xjs.write(((Class)obj).getName());
}
}
}
}