/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.util.fudgemsg;
import static com.google.common.collect.Lists.newArrayList;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.fudgemsg.FudgeField;
import org.fudgemsg.FudgeMsg;
import org.fudgemsg.FudgeRuntimeException;
import org.fudgemsg.MutableFudgeMsg;
import org.fudgemsg.mapping.FudgeBuilder;
import org.fudgemsg.mapping.FudgeDeserializer;
import org.fudgemsg.mapping.FudgeSerializer;
import org.fudgemsg.wire.types.FudgeWireType;
import com.opengamma.OpenGammaRuntimeException;
/**
* Builder to convert inner classes to and from Fudge.
*
* @param <T> the bean type
* @param <K> the type
*/
public final class InnerClassFudgeBuilder<T extends AutoFudgable<K>, K> implements FudgeBuilder<T> {
/**
* Creates a builder for inner class
*/
public InnerClassFudgeBuilder() {}
@Override
public MutableFudgeMsg buildMessage(FudgeSerializer serializer, final T auto) {
MutableFudgeMsg outerMsg = serializer.newMessage();
outerMsg.add(null, FudgeSerializer.TYPES_HEADER_ORDINAL, FudgeWireType.STRING, AutoFudgable.class.getName());
final K inner = auto.object();
assertValid(inner.getClass());
try {
MutableFudgeMsg msg = outerMsg.addSubMessage("inner", null);
//save the internal class name
msg.add(null, FudgeSerializer.TYPES_HEADER_ORDINAL, FudgeWireType.STRING, inner.getClass().getName());
//save the ctor parameters
List<Object> parameters = AccessController.doPrivileged(new PrivilegedAction<List<Object>>() {
@Override
public List<Object> run() {
try {
final Constructor<?>[] ctors = inner.getClass().getDeclaredConstructors();
if (ctors.length != 1) {
throw new IllegalArgumentException("Inner class does not have a single constructor: " + inner.getClass());
}
// find all declared parameters of the inner class
final List<Field> fs = new ArrayList<Field>(Arrays.asList(inner.getClass().getDeclaredFields()));
// remove the field representing the parent object from the list
// only want the compiler synthesized fields which corresponds to ctor parameters
for (Iterator<Field> it = fs.iterator(); it.hasNext(); ) {
Field field = it.next();
if (field.getType().isAssignableFrom(inner.getClass().getEnclosingClass()) || "$jacocoData".equals(field.getName())) {
it.remove();
}
}
final List<Object> parameters = newArrayList();
for (Field paramField : fs) {
paramField.setAccessible(true);
parameters.add(paramField.get(inner));
}
return parameters;
} catch (IllegalAccessException ex) {
throw new OpenGammaRuntimeException(ex.getMessage());
}
}
});
for (Object parameter : parameters) {
//save the ctor parameter
serializer.addToMessageWithClassHeaders(msg, null, 1, parameter);
}
return outerMsg;
} catch (RuntimeException ex) {
throw new FudgeRuntimeException("Unable to serialize: " + inner.getClass().getName(), ex);
}
}
//-------------------------------------------------------------------------
@SuppressWarnings("unchecked")
@Override
public T buildObject(FudgeDeserializer deserializer, FudgeMsg outerMsg) {
FudgeField fudgeField = outerMsg.getByName("inner");
FudgeMsg msg = (FudgeMsg) fudgeField.getValue();
final FudgeField classNameField = msg.getByOrdinal(FudgeSerializer.TYPES_HEADER_ORDINAL);
final String className = (String) classNameField.getValue();
try {
final List<Object> parameters = newArrayList();
parameters.add(null); //the omitted enclosing object
for (FudgeField parameterField : msg.getAllByOrdinal(1)) {
parameters.add(deserializer.fieldValueToObject(parameterField));
}
return (T) AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
final Object[] array = parameters.toArray();
// find constructor
Constructor<?> ctor = null;
try {
final Class<?> innerClass = Class.forName(className);
final Constructor<?>[] ctors = innerClass.getDeclaredConstructors();
if (ctors.length != 1) {
throw new IllegalArgumentException("Inner class does not have a single constructor: " + className);
}
ctor = ctors[0];
ctor.setAccessible(true); // solution
} catch (ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
// invoke constructor
try {
Object inner = ctor.newInstance(array);
return new AutoFudgable<Object>(inner);
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
} catch (InstantiationException ex) {
throw new RuntimeException(ex);
} catch (InvocationTargetException ex) {
throw new RuntimeException(ex);
}
}
});
} catch (RuntimeException ex) {
throw new FudgeRuntimeException("Unable to deserialize: " + className, ex);
}
}
private void assertValid(final Class<?> clazz) {
if (clazz.getEnclosingClass() == null) {
throw new OpenGammaRuntimeException("AutoFudgable can be used only with inner classes");
}
if (clazz.getSuperclass().getEnclosingClass() != null) {
throw new OpenGammaRuntimeException("AutoFudgable can be used only with inner classes which enclosing classes are not inner themselves.");
}
if (!hasSingleZeroArgConstructor(clazz.getSuperclass())) {
throw new OpenGammaRuntimeException("AutoFudgable can be used only with inner classes which enclosing classes have single, zero argument constructor.");
}
}
private static boolean hasSingleZeroArgConstructor(final Class<?> clazz) {
return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
Constructor<?>[] ctors = clazz.getDeclaredConstructors();
return ctors.length == 1 && ctors[0].getParameterTypes().length == 0;
}
});
}
}