/*
* Copyright (C) 2006, 2007, 2008 XStream Committers.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
* Created on 13. April 2006 by Joerg Schaible
*/
package com.thoughtworks.xstream.converters.reflection;
import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.CGLIBMapper;
import com.thoughtworks.xstream.mapper.Mapper;
//import net.sf.cglib.proxy.Callback;
//import net.sf.cglib.proxy.CallbackFilter;
//import net.sf.cglib.proxy.Enhancer;
//import net.sf.cglib.proxy.Factory;
//import net.sf.cglib.proxy.MethodInterceptor;
//import net.sf.cglib.proxy.NoOp;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Converts a proxy created by the CGLIB {@link Enhancer}. Such a proxy is recreated while
* deserializing the proxy. The converter does only work, if<br>
* <ul>
* <li>the DefaultNamingPolicy is used for the proxy's name</li>
* <li>the proxy uses a factory or only one Callback is registered</li>
* <li>a possible super class has at least a protected default constructor</li>
* </ul>
* Note, that the this converter relies on the CGLIBMapper.
*
* @author Jörg Schaible
* @since 1.2
*/
public class CGLIBEnhancedConverter extends SerializableConverter {
private static String DEFAULT_NAMING_MARKER = "$$EnhancerByCGLIB$$";
private static String CALLBACK_MARKER = "CGLIB$CALLBACK_";
private transient Map fieldCache;
public CGLIBEnhancedConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
super(mapper, new CGLIBFilteringReflectionProvider(reflectionProvider));
this.fieldCache = new HashMap();
}
public boolean canConvert(Class type) {
// return (Enhancer.isEnhanced(type) && type.getName().indexOf(DEFAULT_NAMING_MARKER) > 0)
// || type == CGLIBMapper.Marker.class;
return false;
}
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
Class type = source.getClass();
// boolean hasFactory = Factory.class.isAssignableFrom(type);
boolean hasFactory = false;
ExtendedHierarchicalStreamWriterHelper.startNode(writer, "type", type);
context.convertAnother(type.getSuperclass());
writer.endNode();
writer.startNode("interfaces");
Class[] interfaces = type.getInterfaces();
for (int i = 0; i < interfaces.length; i++ ) {
// if (interfaces[i] == Factory.class) {
// continue;
// }
ExtendedHierarchicalStreamWriterHelper.startNode(writer, mapper
.serializedClass(interfaces[i].getClass()), interfaces[i].getClass());
context.convertAnother(interfaces[i]);
writer.endNode();
}
writer.endNode();
writer.startNode("hasFactory");
writer.setValue(String.valueOf(hasFactory));
writer.endNode();
Map callbackIndexMap = null;
// Callback[] callbacks = hasFactory
// ? ((Factory)source).getCallbacks()
// : getCallbacks(source);
// if (callbacks.length > 1) {
// if (hasFactory) {
// callbackIndexMap = createCallbackIndexMap((Factory)source);
// } else {
// ConversionException exception = new ConversionException(
// "Cannot handle CGLIB enhanced proxies without factory that have multiple callbacks");
// exception.add("proxy superclass", type.getSuperclass().getName());
// exception.add("number of callbacks", String.valueOf(callbacks.length));
// throw exception;
// }
// writer.startNode("callbacks");
// writer.startNode("mapping");
// context.convertAnother(callbackIndexMap);
// writer.endNode();
// }
boolean hasInterceptor = false;
// for (int i = 0; i < callbacks.length; i++ ) {
// final Callback callback = callbacks[i];
// if (callback == null) {
// String name = mapper.serializedClass(null);
// writer.startNode(name);
// writer.endNode();
// } else {
// hasInterceptor = hasInterceptor
// || MethodInterceptor.class.isAssignableFrom(callback.getClass());
// ExtendedHierarchicalStreamWriterHelper.startNode(writer, mapper
// .serializedClass(callback.getClass()), callback.getClass());
// context.convertAnother(callback);
// writer.endNode();
// }
// }
// if (callbacks.length > 1) {
// writer.endNode();
// }
try {
final Field field = type.getDeclaredField("serialVersionUID");
field.setAccessible(true);
long serialVersionUID = field.getLong(null);
ExtendedHierarchicalStreamWriterHelper.startNode(
writer, "serialVersionUID", String.class);
writer.setValue(String.valueOf(serialVersionUID));
writer.endNode();
} catch (NoSuchFieldException e) {
// OK, ignore
} catch (IllegalAccessException e) {
throw new ObjectAccessException("Access to serialverionUID of "
+ type.getName()
+ " not allowed");
}
if (hasInterceptor) {
writer.startNode("instance");
super.doMarshalConditionally(source, writer, context);
writer.endNode();
}
}
// private Callback[] getCallbacks(Object source) {
// Class type = source.getClass();
// List fields = (List)fieldCache.get(type.getName());
// if (fields == null) {
// fields = new ArrayList();
// fieldCache.put(type.getName(), fields);
// for (int i = 0; true; ++i) {
// try {
// Field field = type.getDeclaredField(CALLBACK_MARKER + i);
// field.setAccessible(true);
// fields.add(field);
// } catch (NoSuchFieldException e) {
// break;
// }
// }
// }
// List list = new ArrayList();
// for (int i = 0; i < fields.size(); ++i) {
// try {
// Field field = (Field)fields.get(i);
// Object callback = field.get(source);
// list.add(callback);
// } catch (IllegalAccessException e) {
// throw new ObjectAccessException("Access to "
// + type.getName()
// + "."
// + CALLBACK_MARKER
// + i
// + " not allowed");
// }
// }
// return (Callback[])list.toArray(new Callback[list.size()]);
// }
// private Map createCallbackIndexMap(Factory source) {
// Callback[] originalCallbacks = source.getCallbacks();
// Callback[] reverseEngineeringCallbacks = new Callback[originalCallbacks.length];
// Map callbackIndexMap = new HashMap();
// int idxNoOp = -1;
// for (int i = 0; i < originalCallbacks.length; i++ ) {
// Callback callback = originalCallbacks[i];
// if (callback == null) {
// reverseEngineeringCallbacks[i] = null;
// } else if (NoOp.class.isAssignableFrom(callback.getClass())) {
// reverseEngineeringCallbacks[i] = NoOp.INSTANCE;
// idxNoOp = i;
// } else {
// reverseEngineeringCallbacks[i] = createReverseEngineeredCallbackOfProperType(
// callback, i, callbackIndexMap);
// }
// }
//
// try {
// source.setCallbacks(reverseEngineeringCallbacks);
// final Set interfaces = new HashSet();
// final Set methods = new HashSet();
// Class type = source.getClass();
// do {
// methods.addAll(Arrays.asList(type.getDeclaredMethods()));
// methods.addAll(Arrays.asList(type.getMethods()));
// Class[] implementedInterfaces = type.getInterfaces();
// interfaces.addAll(Arrays.asList(implementedInterfaces));
// type = type.getSuperclass();
// } while (type != null);
// for (final Iterator iterator = interfaces.iterator(); iterator.hasNext();) {
// type = (Class)iterator.next();
// methods.addAll(Arrays.asList(type.getDeclaredMethods()));
// }
// for (final Iterator iter = methods.iterator(); iter.hasNext();) {
// final Method method = (Method)iter.next();
// method.setAccessible(true);
// if (Factory.class.isAssignableFrom(method.getDeclaringClass())
// || (method.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) > 0) {
// iter.remove();
// continue;
// }
// Class[] parameterTypes = method.getParameterTypes();
// Method calledMethod = method;
// try {
// if ((method.getModifiers() & Modifier.ABSTRACT) > 0) {
// calledMethod = source.getClass().getMethod(
// method.getName(), method.getParameterTypes());
// }
// callbackIndexMap.put(null, method);
// calledMethod.invoke(source, parameterTypes == null
// ? (Object[])null
// : createNullArguments(parameterTypes));
// } catch (IllegalAccessException e) {
// throw new ObjectAccessException("Access to "
// + calledMethod
// + " not allowed");
// } catch (InvocationTargetException e) {
// // OK, ignore
// } catch (NoSuchMethodException e) {
// ConversionException exception = new ConversionException(
// "CGLIB enhanced proxies wit abstract nethod that has not been implemented");
// exception.add("proxy superclass", type.getSuperclass().getName());
// exception.add("method", method.toString());
// throw exception;
// }
// if (callbackIndexMap.containsKey(method)) {
// iter.remove();
// }
// }
// if (idxNoOp >= 0) {
// Integer idx = new Integer(idxNoOp);
// for (final Iterator iter = methods.iterator(); iter.hasNext();) {
// callbackIndexMap.put(iter.next(), idx);
// }
// }
// } finally {
// source.setCallbacks(originalCallbacks);
// }
//
// callbackIndexMap.remove(null);
// return callbackIndexMap;
// }
private Object[] createNullArguments(Class[] parameterTypes) {
Object[] arguments = new Object[parameterTypes.length];
for (int i = 0; i < arguments.length; i++ ) {
Class type = parameterTypes[i];
if (type.isPrimitive()) {
if (type == byte.class) {
arguments[i] = new Byte((byte)0);
} else if (type == short.class) {
arguments[i] = new Short((short)0);
} else if (type == int.class) {
arguments[i] = new Integer(0);
} else if (type == long.class) {
arguments[i] = new Long(0);
} else if (type == float.class) {
arguments[i] = new Float(0);
} else if (type == double.class) {
arguments[i] = new Double(0);
} else if (type == char.class) {
arguments[i] = new Character('\0');
} else {
arguments[i] = Boolean.FALSE;
}
}
}
return arguments;
}
// private Callback createReverseEngineeredCallbackOfProperType(Callback callback, int index,
// Map callbackIndexMap) {
// Class iface = null;
// Class[] interfaces = callback.getClass().getInterfaces();
// for (int i = 0; i < interfaces.length; i++ ) {
// if (Callback.class.isAssignableFrom(interfaces[i])) {
// iface = interfaces[i];
// if (iface == Callback.class) {
// ConversionException exception = new ConversionException(
// "Cannot handle CGLIB callback");
// exception.add("CGLIB callback type", callback.getClass().getName());
// throw exception;
// }
// interfaces = iface.getInterfaces();
// if (Arrays.asList(interfaces).contains(Callback.class)) {
// break;
// }
// i = -1;
// }
// }
// return (Callback)Proxy.newProxyInstance(
// iface.getClassLoader(), new Class[]{iface},
// new ReverseEngineeringInvocationHandler(index, callbackIndexMap));
// }
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
// final Enhancer enhancer = new Enhancer();
reader.moveDown();
// enhancer.setSuperclass((Class)context.convertAnother(null, Class.class));
reader.moveUp();
reader.moveDown();
List interfaces = new ArrayList();
while (reader.hasMoreChildren()) {
reader.moveDown();
interfaces
.add(context.convertAnother(null, mapper.realClass(reader.getNodeName())));
reader.moveUp();
}
// enhancer.setInterfaces((Class[])interfaces.toArray(new Class[interfaces.size()]));
reader.moveUp();
reader.moveDown();
boolean useFactory = Boolean.valueOf(reader.getValue()).booleanValue();
// enhancer.setUseFactory(useFactory);
reader.moveUp();
List callbacksToEnhance = new ArrayList();
List callbacks = new ArrayList();
Map callbackIndexMap = null;
reader.moveDown();
if ("callbacks".equals(reader.getNodeName())) {
reader.moveDown();
callbackIndexMap = (Map)context.convertAnother(null, HashMap.class);
reader.moveUp();
while (reader.hasMoreChildren()) {
reader.moveDown();
readCallback(reader, context, callbacksToEnhance, callbacks);
reader.moveUp();
}
} else {
readCallback(reader, context, callbacksToEnhance, callbacks);
}
// enhancer.setCallbacks((Callback[])callbacksToEnhance
// .toArray(new Callback[callbacksToEnhance.size()]));
if (callbackIndexMap != null) {
// enhancer.setCallbackFilter(new ReverseEngineeredCallbackFilter(callbackIndexMap));
}
reader.moveUp();
Object result = null;
while (reader.hasMoreChildren()) {
reader.moveDown();
if (reader.getNodeName().equals("serialVersionUID")) {
// enhancer.setSerialVersionUID(Long.valueOf(reader.getValue()));
} else if (reader.getNodeName().equals("instance")) {
// result = create(enhancer, callbacks, useFactory);
super.doUnmarshalConditionally(result, reader, context);
}
reader.moveUp();
}
if (result == null) {
// result = create(enhancer, callbacks, useFactory);
}
return serializationMethodInvoker.callReadResolve(result);
}
private void readCallback(HierarchicalStreamReader reader, UnmarshallingContext context,
List callbacksToEnhance, List callbacks) {
// Callback callback = (Callback)context.convertAnother(null, mapper.realClass(reader
// .getNodeName()));
// callbacks.add(callback);
// if (callback == null) {
// callbacksToEnhance.add(NoOp.INSTANCE);
// } else {
// callbacksToEnhance.add(callback);
// }
}
// private Object create(final Enhancer enhancer, List callbacks, boolean useFactory) {
// Object result = enhancer.create();
// if (useFactory) {
// ((Factory)result).setCallbacks((Callback[])callbacks.toArray(new Callback[callbacks
// .size()]));
// }
// return result;
// }
protected List hierarchyFor(Class type) {
List typeHierarchy = super.hierarchyFor(type);
// drop the CGLIB proxy
typeHierarchy.remove(typeHierarchy.size() - 1);
return typeHierarchy;
}
private Object readResolve() {
fieldCache = new HashMap();
return this;
}
private static class CGLIBFilteringReflectionProvider extends ReflectionProviderWrapper {
public CGLIBFilteringReflectionProvider(final ReflectionProvider reflectionProvider) {
super(reflectionProvider);
}
public void visitSerializableFields(final Object object, final Visitor visitor) {
wrapped.visitSerializableFields(object, new Visitor() {
public void visit(String name, Class type, Class definedIn, Object value) {
if (!name.startsWith("CGLIB$")) {
visitor.visit(name, type, definedIn, value);
}
}
});
}
}
private static final class ReverseEngineeringInvocationHandler implements InvocationHandler {
private final Integer index;
private final Map indexMap;
public ReverseEngineeringInvocationHandler(int index, Map indexMap) {
this.indexMap = indexMap;
this.index = new Integer(index);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
indexMap.put(indexMap.get(null), index);
return null;
}
}
// private static class ReverseEngineeredCallbackFilter implements CallbackFilter {
//
// private final Map callbackIndexMap;
//
// public ReverseEngineeredCallbackFilter(Map callbackIndexMap) {
// this.callbackIndexMap = callbackIndexMap;
// }
//
// public int accept(Method method) {
// if (!callbackIndexMap.containsKey(method)) {
// ConversionException exception = new ConversionException(
// "CGLIB callback not detected in reverse engineering");
// exception.add("CGLIB callback", method.toString());
// throw exception;
// }
// return ((Integer)callbackIndexMap.get(method)).intValue();
// }
//
// }
}