package ru.vyarus.dropwizard.guice.module.context.bootstrap;

import io.dropwizard.Application;
import io.dropwizard.ConfiguredBundle;
import io.dropwizard.setup.Bootstrap;
import org.assertj.core.internal.bytebuddy.ByteBuddy;
import org.assertj.core.internal.bytebuddy.description.modifier.Visibility;
import org.assertj.core.internal.bytebuddy.implementation.InvocationHandlerAdapter;
import org.assertj.core.internal.bytebuddy.implementation.MethodCall;
import org.assertj.core.internal.bytebuddy.matcher.ElementMatchers;
import ru.vyarus.dropwizard.guice.module.context.ConfigurationContext;

/**
 * {@link Bootstrap} proxy delegates all calls directly to bootstrap object, except bundle addition. Instead,
 * bundle addition is replaced with guicey bundle registration so disable and deduplication rules could be
 * applied for transitive dropwizard bundles.
 * <p>
 * Bootstrap proxy object creation adds ~200ms startup overhead, which is clearly visible on
 * stats diagnostic report. Class directly extending {@link Bootstrap} would be much more effective, but it would
 * require overriding of all methods and would be very vulnerable for future bootstrap object changes.
 *
 * @author Vyacheslav Rusakov
 * @since 07.05.2019
 */
public final class BootstrapProxyFactory {

    private BootstrapProxyFactory() {
    }

    /**
     * @param bootstrap dropwizard bootstrap object
     * @param context   guicey configuration context
     * @return dropwizard bootstrap proxy object
     */
    public static Bootstrap create(final Bootstrap bootstrap, final ConfigurationContext context) {
        try {
            return new ByteBuddy()
                    .subclass(Bootstrap.class)
                    .defineConstructor(Visibility.PUBLIC)
                    .intercept(
                            MethodCall.invoke(Bootstrap.class.getDeclaredConstructor(Application.class))
                                    .with(new Object[]{null}))
                    .method(ElementMatchers.any())
                    .intercept(InvocationHandlerAdapter.of((proxy, method, args) -> {
                        // intercept only bundle addition
                        if (method.getName().equals("addBundle")) {
                            context.registerDropwizardBundles((ConfiguredBundle) args[0]);
                            return null;
                        }
                        // other methods called as is
                        return method.invoke(bootstrap, args);
                    }))
                    .make()
                    .load(Bootstrap.class.getClassLoader())
                    .getLoaded()
                    .newInstance();

        } catch (Exception e) {
            throw new IllegalStateException("Failed to create Bootstrap proxy", e);
        }
    }
}

@@ -177,351 +177,76 @@
 ort 
-org.assertj.core.internal.bytebuddy.ByteBuddy;%0Aimport org.assertj.core.internal.bytebuddy.description.modifier.Visibility;%0Aimport org.assertj.core.internal.bytebuddy.implementation.InvocationHandlerAdapter;%0Aimport org.assertj.core.internal.bytebuddy.implementation.MethodCall;%0Aimport org.assertj.core.internal.bytebuddy.matcher.ElementMatchers
+javassist.util.proxy.Proxy;%0Aimport javassist.util.proxy.ProxyFactory
 ;%0Aim
@@ -586,325 +586,8 @@
 es.%0A
- * %3Cp%3E%0A * Bootstrap proxy object creation adds ~200ms startup overhead, which is clearly visible on%0A * stats diagnostic report. Class directly extending %7B@link Bootstrap%7D would be much more effective, but it would%0A * require overriding of all methods and would be very vulnerable for future bootstrap object changes.%0A
  *%0A 
@@ -899,16 +899,51 @@
      */%0A
+    @SuppressWarnings(%22unchecked%22)%0A
     publ
@@ -1064,30 +1064,56 @@
     
-return new ByteBudd
+final ProxyFactory factory = new ProxyFactor
 y()
+;
 %0A   
@@ -1125,20 +1125,24 @@
     
-        .sub
+factory.setSuper
 clas
@@ -1159,16 +1159,17 @@
 p.class)
+;
 %0A       
@@ -1177,232 +1177,149 @@
     
-        .defineConstructor(Visibility.PUBLIC)%0A                    .intercept(%0A                            MethodCall.invoke(Bootstrap.class.getDeclaredConstructor(Application.class))%0A                                    .with
+final Class proxy = factory.createClass();%0A%0A            final Bootstrap res = (Bootstrap) proxy.getConstructor(Application.class).newInstance
 (new
@@ -1334,17 +1334,17 @@
 %5D%7Bnull%7D)
-)
+;
 %0A       
@@ -1352,119 +1352,58 @@
     
-        .method(ElementMatchers.any())%0A                    .intercept(InvocationHandlerAdapter.of((proxy, metho
+((Proxy) res).setHandler((self, thisMethod, procee
 d, a
@@ -1412,24 +1412,16 @@
 s) -%3E %7B%0A
-        
         
@@ -1482,21 +1482,17 @@
     
-    
-    if (m
+if (thisM
 etho
@@ -1522,32 +1522,24 @@
 Bundle%22)) %7B%0A
-        
             
@@ -1625,32 +1625,24 @@
             
- 
-       
 return null;
@@ -1658,30 +1658,18 @@
         
-        %7D%0A    
+%7D%0A
         
@@ -1676,20 +1676,16 @@
         
-    
 // other
@@ -1726,24 +1726,20 @@
     
-        
 return 
-m
+thisM
 etho
@@ -1781,177 +1781,35 @@
     
-        %7D))%0A                    .make()%0A                    .load(Bootstrap.class.getClassLoader())%0A                    .getLoaded()%0A                    .newInstance();%0A
+%7D);%0A            return res;
 %0A   

use javassist for bootstrap proxying