public class DynamicClassException extends RuntimeException {

    public DynamicClassException(final String message, final Throwable cause) {
        super(message, cause);
    }
} @SuppressWarnings("checkstyle:ClassDataAbstractionCoupling")
public final class DynamicClassGenerator {

    /**
     * Postfix applied to interface or abstract class name to get generated class name.
     */
    public static final String DYNAMIC_CLASS_POSTFIX = "$GuiceDynamicClass";

    private DynamicClassGenerator() {
    }

    /**
     * Shortcut for {@link #generate(Class, Class, Class)} method to create default scoped classes.
     * <p>
     * Method is thread safe.
     *
     * @param type interface or abstract class
     * @param <T>  type
     * @return implementation class for provided type (will not generate if class already exist)
     */
    public static <T> Class<T> generate(final Class<T> type) {
        return generate(type, null);
    }

    /**
     * Shortcut for {@link #generate(Class, Class, Class)} method to create classes with provided scope
     * (and without extra anchor).
     * <p>
     * Method is thread safe.
     *
     * @param type  interface or abstract class
     * @param scope scope annotation to apply on generated class (may be null for default prototype scope)
     * @param <T>   type
     * @return implementation class for provided type (will not generate if class already exist)
     */
    public static <T> Class<T> generate(final Class<T> type,
                                        final Class<? extends java.lang.annotation.Annotation> scope) {
        return generate(type, scope, null);
    }

    /**
     * Generates dynamic class, which guice may use as implementation and generate proxy above it,
     * correctly applying aop features.
     * <p>
     * New class will inherit type annotations and constructor with annotations
     * (if base class use constructor injection). Also constructor inherits all annotations, including
     * parameters annotations. If anchor is provided then it will be added as last constructor parameter
     * or (when abstract type has no constructor) new constructor added with one parameter (anchor).
     * <p>
     * Method is thread safe.
     *
     * @param type   interface or abstract class
     * @param scope  scope annotation to apply on generated class (may be null for default prototype scope)
     * @param anchor existing binding to depend generated class on (to prevent binding bubbling up to root injector)
     * @param <T>    type
     * @return implementation class for provided type (will not generate if class already exist)
     * @see ru.vyarus.guice.ext.core.generator.anchor.GeneratorAnchorModule for more details about anchor usage
     */
    @SuppressWarnings("unchecked")
    public static <T> Class<T> generate(final Class<T> type,
                                        final Class<? extends java.lang.annotation.Annotation> scope,
                                        final Class<?> anchor) {
        Preconditions.checkNotNull(type, "Original type required");
        Preconditions.checkArgument(type.isInterface() || Modifier.isAbstract(type.getModifiers()),
                "Type must be interface or abstract class, but provided type is not: %s", type.getName());

        final String targetClassName = type.getName() + DYNAMIC_CLASS_POSTFIX;
        final ClassLoader classLoader = type.getClassLoader();

        /*
         * Synchronization is required to avoid double generation and consequent problems.
         * Very unlikely that this method would be called too often and synchronization become bottleneck.
         * Using original class as monitor to allow concurrent generation for different classes.
         */
        synchronized (type) {
            Class<?> targetClass;
            try {
                // will work if class was already generated
                targetClass = classLoader.loadClass(targetClassName);
            } catch (ClassNotFoundException ex) {
                targetClass = generateClass(type, targetClassName, classLoader, scope, anchor);
            }
            return (Class<T>) targetClass;
        }
    }

    private static Class<?> generateClass(final Class<?> type, final String targetClassName,
                                          final ClassLoader classLoader,
                                          final Class<? extends java.lang.annotation.Annotation> scope,
                                          final Class<?> anchor) {
        try {
            // have to use custom pool because original type classloader could be thrown away
            // and all cached CtClass objects would be stale
            final ClassPool classPool = new ClassPool();
            classPool.appendClassPath(new LoaderClassPath(classLoader));

            final CtClass impl = generateCtClass(classPool, targetClassName, type, scope, anchor);
            // incompatible javassist apis for java 8 and >=9
            return Utils.isJava8() ? impl.toClass(classLoader, type.getProtectionDomain()) : impl.toClass(type);
        } catch (Exception ex) {
            throw new DynamicClassException("Failed to generate class for " + type.getName(), ex);
        }
    }

    private static CtClass generateCtClass(final ClassPool classPool, final String targetClassName, final Class type,
                                           final Class<? extends java.lang.annotation.Annotation> scope,
                                           final Class<?> anchor)
            throws Exception {

        final CtClass ctType = classPool.get(type.getName());
        final CtClass ctAnchor = anchor == null ? null : classPool.getCtClass(anchor.getName());
        final CtClass impl;
        if (type.isInterface()) {
            impl = classPool.makeClass(targetClassName);
            impl.addInterface(ctType);
        } else {
            impl = classPool.makeClass(targetClassName, ctType);
            final Constructor diConstructor = findDIConstructor(type);
            if (diConstructor != null) {
                copyConstructor(impl, ctType, diConstructor, ctAnchor);
            }
        }
        if (anchor != null && impl.getConstructors().length == 0) {
            // create new constructor with anchor dependency
            createAnchorConstructor(impl, ctAnchor);
        }
        final ConstPool constPool = impl.getClassFile().getConstPool();
        final AnnotationsAttribute annotations = copyAnnotations(classPool, constPool, type);
        impl.getClassFile().addAttribute(annotations);
        applyScopeAnnotation(classPool, annotations, type, scope);
        return impl;
    }

    private static void copyConstructor(final CtClass impl, final CtClass ctType, final Constructor ctor,
                                        final CtClass anchor) throws Exception {
        final ClassPool classPool = impl.getClassPool();
        final CtClass[] parameters = JavassistUtils.convertTypes(classPool, ctor.getParameterTypes());
        final CtConstructor ctConstructor = CtNewConstructor.make(
                parameters,
                JavassistUtils.convertTypes(classPool, ctor.getExceptionTypes()),
                CtNewConstructor.PASS_PARAMS, null, null, impl);
        if (anchor != null) {
            ctConstructor.addParameter(anchor);
        }
        final ConstPool constPool = impl.getClassFile().getConstPool();
        final MethodInfo methodInfo = ctConstructor.getMethodInfo();
        methodInfo.addAttribute(copyAnnotations(classPool, constPool, ctor));
        methodInfo.addAttribute(copyConstructorParametersAnnotations(classPool, constPool, ctor, anchor != null));
        final SignatureAttribute info = copyConstructorGenericsSignature(constPool, parameters, ctType, anchor);
        if (info != null) {
            methodInfo.addAttribute(info);
        }
        impl.addConstructor(ctConstructor);
    }

    private static void createAnchorConstructor(final CtClass impl, final CtClass anchor)
            throws Exception {
        final ClassPool classPool = impl.getClassPool();
        final CtConstructor ctConstructor = CtNewConstructor.make(
                new CtClass[]{anchor},
                null,
                CtNewConstructor.PASS_NONE, null, null, impl);
        final ConstPool constPool = impl.getClassFile().getConstPool();
        final MethodInfo methodInfo = ctConstructor.getMethodInfo();
        // add injection annotation
        final AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        final Annotation annotation = new Annotation(constPool,
                classPool.get(javax.inject.Inject.class.getName()));
        attr.addAnnotation(annotation);
        methodInfo.addAttribute(attr);
        impl.addConstructor(ctConstructor);
    }

    private static Constructor findDIConstructor(final Class<?> type) {
        Constructor target = null;
        for (Constructor ctor : type.getConstructors()) {
            if (ctor.isAnnotationPresent(Inject.class) || ctor.isAnnotationPresent(javax.inject.Inject.class)) {
                target = ctor;
                break;
            }
        }
        return target;
    }

    @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
    private static ParameterAnnotationsAttribute copyConstructorParametersAnnotations(
            final ClassPool classPool, final ConstPool constPool, final Constructor ctor,
            final boolean anchorAdded) throws Exception {
        final int count = ctor.getParameterTypes().length;
        final Annotation[][] paramAnnotations = new Annotation[count + (anchorAdded ? 1 : 0)][];
        for (int i = 0; i < count; i++) {
            final java.lang.annotation.Annotation[] anns = ctor.getParameterAnnotations()[i];
            paramAnnotations[i] = new Annotation[anns.length];
            for (int j = 0; j < anns.length; j++) {
                paramAnnotations[i][j] = JavassistUtils.copyAnnotation(classPool, constPool, anns[j]);
            }
        }
        if (anchorAdded) {
            paramAnnotations[count] = new Annotation[0];
        }
        final ParameterAnnotationsAttribute paramAnns = new ParameterAnnotationsAttribute(
                constPool, ParameterAnnotationsAttribute.visibleTag);
        paramAnns.setAnnotations(paramAnnotations);
        return paramAnns;
    }

    @SuppressWarnings("PMD.UnusedPrivateMethod")
    private static void applyScopeAnnotation(final ClassPool classPool, final AnnotationsAttribute annotations,
                                             final AnnotatedElement source,
                                             final Class<? extends java.lang.annotation.Annotation> scope)
            throws Exception {
        if (scope != null) {
            Preconditions.checkState(Annotations.isScopeAnnotation(scope),
                    "Provided annotation %s is not scope annotation", scope.getSimpleName());
            for (java.lang.annotation.Annotation ann : source.getAnnotations()) {
                Preconditions.checkArgument(!(ann instanceof ScopeAnnotation),
                        "Duplicate scope definition: scope is specified as %s and also defined "
                                + "in @ScopeAnnotation.", scope.getSimpleName());
            }
            annotations.addAnnotation(new Annotation(annotations.getConstPool(), classPool.get(scope.getName())));
        }
    }

    private static AnnotationsAttribute copyAnnotations(final ClassPool classPool, final ConstPool constPool,
                                                        final AnnotatedElement source) throws Exception {
        final AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        if (source.getAnnotations().length > 0) {

            for (java.lang.annotation.Annotation ann : source.getAnnotations()) {
                final Annotation annotation = processAnnotation(classPool, constPool, ann);
                if (annotation != null) {
                    attr.addAnnotation(annotation);
                }
            }
        }
        return attr;
    }

    private static Annotation processAnnotation(final ClassPool classPool, final ConstPool constPool,
                                                final java.lang.annotation.Annotation ann) throws Exception {
        Annotation res = null;
        // if we copy these annotation guice will go to infinite loop
        if (!(ann instanceof ProvidedBy || ann instanceof ImplementedBy)) {
            Preconditions.checkState(!Annotations.isScopeAnnotation(ann.annotationType()),
                    "Don't use scope annotations directly - use @ScopeAnnotation(TargetScope) wrapper, "
                            + "because guice doesn't allow scope annotations on abstract types");
            if (ann instanceof ScopeAnnotation) {
                res = new Annotation(constPool,
                        classPool.get(((ScopeAnnotation) ann).value().getName()));
            } else {
                res = JavassistUtils.copyAnnotation(classPool, constPool, ann);
            }
        }
        return res;
    }

    private static SignatureAttribute copyConstructorGenericsSignature(
            final ConstPool constPool, final CtClass[] params, final CtClass source, final CtClass anchor)
            throws Exception {
        final CtConstructor ctConstructor = source.getConstructor(Descriptor.ofConstructor(params));
        String signature = null;
        for (Object attr : ctConstructor.getMethodInfo().getAttributes()) {
            if (attr instanceof SignatureAttribute) {
                signature = ((SignatureAttribute) attr).getSignature();
                break;
            }
        }
        if (signature != null && anchor != null) {
            // add anchor to generics signature
            final String type = "L" + (anchor.getName().replaceAll("\\.", "/")) + ";";
            final int idx = signature.lastIndexOf(')');
            signature = signature.substring(0, idx) + type + signature.substring(idx);
        }
        return signature == null ? null : new SignatureAttribute(constPool, signature);
    }
} public class CustomAopInterceptor implements MethodInterceptor{

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        return "I'm intercepted!";
    }
} @Singleton
@ProvidedBy(DynamicClassProvider.class)
public abstract class BadDeclarationBean {
} @ScopeAnnotation(Singleton.class)
@ProvidedBy(DynamicSingletonProvider.class)
public interface BadSingletonDeclaration {
} @ProvidedBy(DynamicClassProvider.class)
public class WrongUsageBean {
} @ProvidedBy(DynamicClassProvider.class)
public abstract class CompositeCase1 {

    public String self(){
        return "self";
    }

    @CustomAop
    public abstract String hello();
} @ProvidedBy(DynamicClassProvider.class)
public abstract class CompositeCase2 implements InterfaceBean {

    public String self(){
        return "self";
    }
} @ProvidedBy(DynamicClassProvider.class)
public abstract class CustomConstructorBean {

    ProvidedInterfaceBean bean;

    @Inject
    @Ann("ctor")
    public CustomConstructorBean(@Ann("param") ProvidedInterfaceBean bean) throws Exception {
        this.bean = bean;
    }

    public String hello() {
        return bean.hello();
    }
} @ProvidedBy(DynamicSingletonProvider.class)
public abstract class GenerifiedConstructorBean {

    Provider<ProvidedInterfaceBean> beanProvider;

    @Inject
    public GenerifiedConstructorBean(Provider<ProvidedInterfaceBean> beanProvider) throws Exception {
        this.beanProvider = beanProvider;
    }

    public String hello() {
        return beanProvider.get().hello();
    }
}
import com.google.inject.*;
import com.google.inject.matcher.Matchers;
import org.junit.Assert;
import org.junit.Test;
import ru.vyarus.guice.ext.core.generator.DynamicClassException;
import ru.vyarus.guice.ext.core.generator.DynamicClassGenerator;
import ru.vyarus.guice.ext.generator.support.*;
import ru.vyarus.guice.ext.generator.support.aop.CustomAop;
import ru.vyarus.guice.ext.generator.support.aop.CustomAopInterceptor;
import ru.vyarus.guice.ext.generator.support.bad.BadDeclarationBean;
import ru.vyarus.guice.ext.generator.support.bad.BadSingletonDeclaration;
import ru.vyarus.guice.ext.generator.support.bad.WrongUsageBean;
import ru.vyarus.guice.ext.generator.support.composite.CompositeCase1;
import ru.vyarus.guice.ext.generator.support.composite.CompositeCase2;
import ru.vyarus.guice.ext.generator.support.ctor.Ann;
import ru.vyarus.guice.ext.generator.support.ctor.CustomConstructorBean;
import ru.vyarus.guice.ext.generator.support.ctor.GenerifiedConstructorBean;
import javax.inject.Singleton;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
        assertTrue(Arrays.equals(ann.arr(), new int[]{1, 2, 3}));
        assertTrue(Arrays.equals(ann.arrBool(), new boolean[]{true, false}));
        assertTrue(Arrays.equals(ann.arrByte(), new byte[]{1, 2}));
        assertTrue(Arrays.equals(ann.arrChar(), new char[]{',', 'l'}));
        assertTrue(Arrays.equals(ann.arrDbl(), new double[]{0.1, 0.2}));
        assertTrue(Arrays.equals(ann.arrFlt(), new float[]{0.1f, 0.2f}));
        assertTrue(Arrays.equals(ann.arrLng(), new long[]{1l, 2l}));
        assertTrue(Arrays.equals(ann.arrObj(), new String[]{"tst", "tst2"}));
        assertTrue(ann.bool());
        assertTrue(ann.bte() == 1);
        assertTrue(ann.chr() == ',');
        assertTrue(ann.dbl() == 1d);
        assertTrue(ann.integer() == 1);
        assertTrue(ann.lng() == 1l);
        assertTrue(ann.flt() == 1f);
        assertTrue(ann.srt() == 1);
        assertTrue(ann.str().equals("string"));
        assertTrue(ann.cls() == InterfaceBean.class);
        assertTrue(ann.enm() == ElementType.TYPE);
    }

    @Test(expected = DynamicClassException.class)
    public void testDirectScopeAnnotation() throws Exception {
        // scope annotation can't be used directly
        DynamicClassGenerator.generate(BadDeclarationBean.class);
    }

    @Test
    public void testAop() throws Exception {
        Injector injector = Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                bind(InterfaceBean.class).to(DynamicClassGenerator.generate(InterfaceBean.class));
                bind(AbstractBean.class).to(DynamicClassGenerator.generate(AbstractBean.class));
                bindInterceptor(Matchers.any(), Matchers.annotatedWith(CustomAop.class), new CustomAopInterceptor());
            }
        });
        injector.getInstance(InterfaceBean.class).hello();
        injector.getInstance(AbstractBean.class).hello();
        injector.getInstance(ProvidedInterfaceBean.class).hello();
        injector.getInstance(ProvidedAbstractBean.class).hello();
    }

    @Test(expected = AbstractMethodError.class)
    public void testNoAopCall() throws Exception {
        Guice.createInjector().getInstance(ProvidedInterfaceBean.class).badMethod();
    }

    @Test
    public void testCompositeCases() throws Exception {
        Injector injector = Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                bindInterceptor(Matchers.any(), Matchers.annotatedWith(CustomAop.class), new CustomAopInterceptor());
            }
        });
        CompositeCase1 case1 = injector.getInstance(CompositeCase1.class);
        CompositeCase2 case2 = injector.getInstance(CompositeCase2.class);

        case1.self();
        case1.hello();

        case2.self();
        case2.hello();
    }

    @Test(expected = ProvisionException.class)
    public void testWrongProviderUsage() throws Exception {
        Guice.createInjector().getInstance(WrongUsageBean.class);
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testCustomConstructor() throws Exception {
        // original type constructor copied and constructor injection correctly handled
        Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                bindInterceptor(Matchers.any(), Matchers.annotatedWith(CustomAop.class), new CustomAopInterceptor());
            }
        }).getInstance(CustomConstructorBean.class).hello();
        Class<?> generated = DynamicClassGenerator.generate(CustomConstructorBean.class);
        assertEquals(1, generated.getConstructors().length);
        Constructor ctor = generated.getConstructors()[0];
        assertTrue(ctor.isAnnotationPresent(Inject.class));
        assertTrue(ctor.isAnnotationPresent(Ann.class));
        assertEquals("ctor", ((Ann) ctor.getAnnotation(Ann.class)).value());
        assertEquals(1, ctor.getExceptionTypes().length);
        assertEquals(1, ctor.getParameterAnnotations()[0].length);
        Annotation ann = ctor.getParameterAnnotations()[0][0];
        assertTrue(ann instanceof Ann);
        assertEquals("param", ((Ann) ann).value());
    }

    @Test
    public void testGenerifiedConstructor() throws Exception {
        // original type constructor copied and constructor injection correctly handled
        Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                bindInterceptor(Matchers.any(), Matchers.annotatedWith(CustomAop.class), new CustomAopInterceptor());
            }
        }).getInstance(GenerifiedConstructorBean.class).hello();
    }

    @Test
    public void testSingletonProvider() throws Exception {
        Injector injector = Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                bindInterceptor(Matchers.any(), Matchers.annotatedWith(CustomAop.class), new CustomAopInterceptor());
            }
        });
        GenerifiedConstructorBean bean1 = injector.getInstance(GenerifiedConstructorBean.class);
        GenerifiedConstructorBean bean2 = injector.getInstance(GenerifiedConstructorBean.class);
        Assert.assertTrue(bean1 == bean2);
    }

    @Test(expected = ProvisionException.class)
    public void testDuplicateSingletonDeclaration() throws Exception {
        Guice.createInjector().getInstance(BadSingletonDeclaration.class);