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 static <T> T proxy(Class<T> type, Callback callback) {
    callback = callback != null ? callback : BLOCKER;

    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(type);
    enhancer.setCallbackType(callback.getClass());

    @SuppressWarnings("unchecked")
    Class<T> proxyClass = enhancer.createClass();

    @SuppressWarnings("unchecked")
    T proxy = (T) ObjenesisHelper.newInstance(proxyClass);
    ((Factory) proxy).setCallback(0, callback);
    return proxy;
  }
 public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
   Class type = source.getClass();
   boolean hasFactory = Factory.class.isAssignableFrom(type);
   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 serialVersionUID of " + type.getName() + " not allowed");
   }
   if (hasInterceptor) {
     writer.startNode("instance");
     super.doMarshalConditionally(source, writer, context);
     writer.endNode();
   }
 }
  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;
  }