// Path: src/main/java/com/github/vbauer/caesar/exception/MissedSyncMethodException.java
// @SuppressWarnings("serial")
// public class MissedSyncMethodException extends AbstractCaesarException {
// 
//     private final Method method;
//     private final Object[] arguments;
// 
// 
//     public MissedSyncMethodException(final Method method, final Object... arguments) {
//         this.method = method;
//         this.arguments = arguments;
//     }
// 
// 
//     public Method getMethod() {
//         return method;
//     }
// 
//     public Object[] getArguments() {
//         return arguments;
//     }
// 
// 
//     /**
//      * {@inheritDoc}
//      */
//     @Override
//     public String getMessage() {
//         return String.format(
//             "Can not find appropriate sync-method \"%s\", parameters: %s",
//             getMethod(), Arrays.toString(getArguments())
//         );
//     }
// 
// }
// 
// Path: src/main/java/com/github/vbauer/caesar/runner/AsyncMethodRunner.java
// public interface AsyncMethodRunner {
// 
//     Method findSyncMethod(Object origin, Method asyncMethod);
// 
//     <T> Callable<T> createCall(Object origin, Method syncMethod, Object[] args);
// 
//     Object processResultFuture(Future<?> future, ExecutorService executor) throws Throwable;
// 
// }
// 
// Path: src/main/java/com/github/vbauer/caesar/util/ReflectionUtils.java
// @SuppressWarnings("unchecked")
// public final class ReflectionUtils {
// 
//     public static final String PACKAGE_SEPARATOR = ".";
//     
// 
//     private ReflectionUtils() {
//         throw new UnsupportedOperationException();
//     }
// 
// 
//     public static <T> Class<T> getClassWithoutProxies(final Object object) {
//         try {
//             // XXX: Use HibernateProxyHelper to un-proxy object and get the original class.
//             return (Class<T>) Class.forName("org.hibernate.proxy.HibernateProxyHelper")
//                 .getDeclaredMethod("getClassWithoutInitializingProxy", Object.class)
//                 .invoke(null, object);
//         } catch (final Exception ex) {
//             return getClassSafe(object);
//         }
//     }
// 
//     public static <T> Class<T> getClassSafe(final Object object) {
//         return object != null ? (Class<T>) object.getClass() : null;
//     }
// 
//     public static <T> T createObject(final String className) {
//         try {
//             final Class<?> clazz = Class.forName(className);
//             return (T) clazz.newInstance();
//         } catch (final Throwable ex) {
//             return null;
//         }
//     }
// 
//     public static <T> Collection<T> createObjects(final Collection<String> classNames) {
//         final List<T> objects = new ArrayList<>();
//         for (final String className : classNames) {
//             final T object = createObject(className);
//             if (object != null) {
//                 objects.add(object);
//             }
//         }
//         return objects;
//     }
//     
//     public static Collection<String> classNames(final String packageName, final Collection<String> classNames) {
//         final List<String> result = new ArrayList<>();
//         for (final String className : classNames) {
//             result.add(packageName + PACKAGE_SEPARATOR + className);
//         }
//         return result;        
//     }
// 
//     public static Method findDeclaredMethod(
//         final Class<?> objectClass, final String methodName, final Class<?>[] parameterTypes
//     ) {
//         try {
//             return objectClass.getDeclaredMethod(methodName, parameterTypes);
//         } catch (final Throwable ignored) {
//             return null;
//         }
//     }
// 
//     public static <T extends Annotation> T findAnnotationFromMethodOrClass(
//         final Method method, final Class<T> annotationClass
//     ) {
//         final T annotation = method.getAnnotation(annotationClass);
//         if (annotation != null) {
//             return annotation;
//         }
// 
//         final Class<?> originClass = method.getDeclaringClass();
//         return originClass.getAnnotation(annotationClass);
//     }
// 
// }


import com.github.vbauer.caesar.exception.MissedSyncMethodException;
import com.github.vbauer.caesar.runner.AsyncMethodRunner;
import com.github.vbauer.caesar.util.ReflectionUtils;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.ExecutorService;
package com.github.vbauer.caesar.proxy;



/**
 * Proxy creator that makes asynchronous variant of bean.
 *
 * @author Vladislav Bauer
 */

public final class AsyncProxyCreator {

    private AsyncProxyCreator() {
        throw new UnsupportedOperationException();
    }


    public static <SYNC, ASYNC> ASYNC create(
        final SYNC bean, final Class<ASYNC> asyncInterface, final ExecutorService executor
    ) {
        return create(bean, asyncInterface, executor, true);
    }

    @SuppressWarnings("unchecked")
    public static <SYNC, ASYNC> ASYNC create(
        final SYNC bean, final Class<ASYNC> asyncInterface, final ExecutorService executor, final boolean validate
    ) {
        final AsyncInvocationHandler handler = AsyncInvocationHandler.create(bean, executor);
        final Class<?> beanClass = bean.getClass();
        final ClassLoader classLoader = beanClass.getClassLoader();
        final Class<?>[] interfaces = {
            asyncInterface,
        };

        final ASYNC proxy = (ASYNC) Proxy.newProxyInstance(classLoader, interfaces, handler);
        return validate ? validate(proxy, handler) : proxy;
    }


    private static <T> T validate(final T proxy, final AsyncInvocationHandler handler) {
        final Class<?> targetClass = ReflectionUtils.getClassWithoutProxies(proxy);
        final Method[] methods = targetClass.getDeclaredMethods();

        for (final Method method : methods) {
// Path: src/main/java/com/github/vbauer/caesar/exception/MissedSyncMethodException.java
// @SuppressWarnings("serial")
// public class MissedSyncMethodException extends AbstractCaesarException {
// 
//     private final Method method;
//     private final Object[] arguments;
// 
// 
//     public MissedSyncMethodException(final Method method, final Object... arguments) {
//         this.method = method;
//         this.arguments = arguments;
//     }
// 
// 
//     public Method getMethod() {
//         return method;
//     }
// 
//     public Object[] getArguments() {
//         return arguments;
//     }
// 
// 
//     /**
//      * {@inheritDoc}
//      */
//     @Override
//     public String getMessage() {
//         return String.format(
//             "Can not find appropriate sync-method \"%s\", parameters: %s",
//             getMethod(), Arrays.toString(getArguments())
//         );
//     }
// 
// }
// 
// Path: src/main/java/com/github/vbauer/caesar/runner/AsyncMethodRunner.java
// public interface AsyncMethodRunner {
// 
//     Method findSyncMethod(Object origin, Method asyncMethod);
// 
//     <T> Callable<T> createCall(Object origin, Method syncMethod, Object[] args);
// 
//     Object processResultFuture(Future<?> future, ExecutorService executor) throws Throwable;
// 
// }
// 
// Path: src/main/java/com/github/vbauer/caesar/util/ReflectionUtils.java
// @SuppressWarnings("unchecked")
// public final class ReflectionUtils {
// 
//     public static final String PACKAGE_SEPARATOR = ".";
//     
// 
//     private ReflectionUtils() {
//         throw new UnsupportedOperationException();
//     }
// 
// 
//     public static <T> Class<T> getClassWithoutProxies(final Object object) {
//         try {
//             // XXX: Use HibernateProxyHelper to un-proxy object and get the original class.
//             return (Class<T>) Class.forName("org.hibernate.proxy.HibernateProxyHelper")
//                 .getDeclaredMethod("getClassWithoutInitializingProxy", Object.class)
//                 .invoke(null, object);
//         } catch (final Exception ex) {
//             return getClassSafe(object);
//         }
//     }
// 
//     public static <T> Class<T> getClassSafe(final Object object) {
//         return object != null ? (Class<T>) object.getClass() : null;
//     }
// 
//     public static <T> T createObject(final String className) {
//         try {
//             final Class<?> clazz = Class.forName(className);
//             return (T) clazz.newInstance();
//         } catch (final Throwable ex) {
//             return null;
//         }
//     }
// 
//     public static <T> Collection<T> createObjects(final Collection<String> classNames) {
//         final List<T> objects = new ArrayList<>();
//         for (final String className : classNames) {
//             final T object = createObject(className);
//             if (object != null) {
//                 objects.add(object);
//             }
//         }
//         return objects;
//     }
//     
//     public static Collection<String> classNames(final String packageName, final Collection<String> classNames) {
//         final List<String> result = new ArrayList<>();
//         for (final String className : classNames) {
//             result.add(packageName + PACKAGE_SEPARATOR + className);
//         }
//         return result;        
//     }
// 
//     public static Method findDeclaredMethod(
//         final Class<?> objectClass, final String methodName, final Class<?>[] parameterTypes
//     ) {
//         try {
//             return objectClass.getDeclaredMethod(methodName, parameterTypes);
//         } catch (final Throwable ignored) {
//             return null;
//         }
//     }
// 
//     public static <T extends Annotation> T findAnnotationFromMethodOrClass(
//         final Method method, final Class<T> annotationClass
//     ) {
//         final T annotation = method.getAnnotation(annotationClass);
//         if (annotation != null) {
//             return annotation;
//         }
// 
//         final Class<?> originClass = method.getDeclaringClass();
//         return originClass.getAnnotation(annotationClass);
//     }
// 
// }


// Path: src/main/java/com/github/vbauer/caesar/proxy/AsyncProxyCreator.java
import com.github.vbauer.caesar.exception.MissedSyncMethodException;
import com.github.vbauer.caesar.runner.AsyncMethodRunner;
import com.github.vbauer.caesar.util.ReflectionUtils;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.ExecutorService;

package com.github.vbauer.caesar.proxy;



/**
 * Proxy creator that makes asynchronous variant of bean.
 *
 * @author Vladislav Bauer
 */

public final class AsyncProxyCreator {

    private AsyncProxyCreator() {
        throw new UnsupportedOperationException();
    }


    public static <SYNC, ASYNC> ASYNC create(
        final SYNC bean, final Class<ASYNC> asyncInterface, final ExecutorService executor
    ) {
        return create(bean, asyncInterface, executor, true);
    }

    @SuppressWarnings("unchecked")
    public static <SYNC, ASYNC> ASYNC create(
        final SYNC bean, final Class<ASYNC> asyncInterface, final ExecutorService executor, final boolean validate
    ) {
        final AsyncInvocationHandler handler = AsyncInvocationHandler.create(bean, executor);
        final Class<?> beanClass = bean.getClass();
        final ClassLoader classLoader = beanClass.getClassLoader();
        final Class<?>[] interfaces = {
            asyncInterface,
        };

        final ASYNC proxy = (ASYNC) Proxy.newProxyInstance(classLoader, interfaces, handler);
        return validate ? validate(proxy, handler) : proxy;
    }


    private static <T> T validate(final T proxy, final AsyncInvocationHandler handler) {
        final Class<?> targetClass = ReflectionUtils.getClassWithoutProxies(proxy);
        final Method[] methods = targetClass.getDeclaredMethods();

        for (final Method method : methods) {
            final AsyncMethodRunner runner = handler.findAsyncMethodRunner(method);