// Path: src/main/java/no/saua/remock/internal/AnnotationVisitor.java
// public interface AnnotationVisitor<T extends Annotation> {
//     AnnotationVisitorResult visitClass(T annotation);
// 
//     AnnotationVisitorResult visitField(T annotation, Field field);
// 
//     public static class AnnotationVisitorResult {
//         private Set<SpringBeanDefiner> definers = new HashSet<>();
//         private Set<SpyDefinition> spies = new HashSet<>();
//         private Set<Rejecter> rejecters = new HashSet<>();
// 
//         public AnnotationVisitorResult addSpringBeanDefiner(SpringBeanDefiner springBeanDefiner) {
//             definers.add(springBeanDefiner);
//             return this;
//         }
// 
//         public AnnotationVisitorResult addSpy(SpyDefinition spyDefinition) {
//             spies.add(spyDefinition);
//             return this;
//         }
// 
//         public AnnotationVisitorResult addRejecter(Rejecter rejecter) {
//             rejecters.add(rejecter);
//             return this;
//         }
// 
//         Set<SpringBeanDefiner> getDefiners() {
//             return definers;
//         }
// 
//         Set<SpyDefinition> getSpies() {
//             return spies;
//         }
// 
//         Set<Rejecter> getRejecters() {
//             return rejecters;
//         }
//     }
// }
// 
// Path: src/main/java/no/saua/remock/internal/RegularDefinition.java
// public class RegularDefinition extends Entity<RegularDefinition> implements SpringBeanDefiner {
// 
//     private final Class<?> beanClass;
//     private final String beanName;
// 
//     public RegularDefinition(Class<?> beanClass, String beanName) {
//         this.beanClass = beanClass;
//         this.beanName = beanName;
//     }
// 
//     @Override
//     public BeanDefinition getBeanDefinition() {
//         GenericBeanDefinition beanDef = new GenericBeanDefinition();
//         beanDef.setBeanClass(beanClass);
//         return beanDef;
//     }
// 
//     @Override
//     public String getBeanName() {
//         return beanName;
//     }
// 
//     @Override
//     public int hashCode() {
//         return Objects.hash(beanClass, beanName);
//     }
// 
//     @Override
//     public boolean equals(RegularDefinition other) {
//         return Objects.equals(beanClass, other.beanClass) && Objects.equals(beanName, other.beanName);
//     }
// }
// 
// Path: src/main/java/no/saua/remock/internal/RejectBeanClassDefinition.java
// public class RejectBeanClassDefinition extends Entity<RejectBeanClassDefinition> implements Rejecter {
// 
//     private final Class<?> rejectClass;
//     private final List<Class<?>> exceptClasses;
// 
//     public RejectBeanClassDefinition(Class<?> rejectClass) {
//         this(rejectClass, Collections.<Class<?>>emptyList());
//     }
// 
//     public RejectBeanClassDefinition(Class<?> rejectClass, List<Class<?>> exceptClasses) {
//         this.rejectClass = Objects.requireNonNull(rejectClass);
//         this.exceptClasses = Objects.requireNonNull(exceptClasses);
// 
//     }
// 
//     @Override
//     public boolean shouldReject(String beanName, Class<?> beanClass) {
//         if (beanClass == null || !rejectClass.isAssignableFrom(beanClass)) {
//             // -> Class does not match
//             return false;
//         }
// 
//         // E-> Class matches
//         for (Class<?> clazz : exceptClasses) {
//             if (clazz.isAssignableFrom(beanClass)) {
//                 return false;
//             }
//         }
// 
//         return true;
//     }
// 
//     @Override
//     public boolean equals(RejectBeanClassDefinition other) {
//         return Objects.equals(rejectClass, other.rejectClass) && Objects.equals(exceptClasses, other.exceptClasses);
//     }
// 
//     @Override
//     public int hashCode() {
//         return Objects.hash(rejectClass, exceptClasses);
//     }
// }


import no.saua.remock.internal.AnnotationVisitor;
import no.saua.remock.internal.RegularDefinition;
import no.saua.remock.internal.RejectBeanClassDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import java.lang.annotation.*;
import java.lang.reflect.Field;
package no.saua.remock;



/**
 * Rejects any class with the class {@link #value()} and replaces it with a {@link #with()}. Useful
 * if you want to actually implement a complete mock class. Note that this annotation also is a
 * meta-annotation for {@link Autowired} causing the annotated field to automatically be injected.
 * Usage:
 * <p>
 * <pre>
 * &#064;RunWith(SpringJUnit4ClassRunner.class)
 * &#064;BootstrapWith(RemockBootstrapper.class)
 * &#064;ContextConfiguration(classes = {ServiceImpl.class})
 * public static class ReplaceWithImplAnnotatedOnFieldTest {
 *
 *     &#064;ReplaceWithImpl(value = ServiceImpl.class, with = ServiceMock.class)
 *     public Service service;
 *
 *     &#064;Test
 *     public void test() {
 *         assertEquals(ServiceMock.class, service.getClass());
 *     }
 * }
 * </pre>
 */
@Autowired
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(ReplaceWithImpl.ReplaceWithImpls.class)
public @interface ReplaceWithImpl {

    public static final String DEFAULT_BEAN_NAME = "::REMOCK_DEFAULT_BEAN_NAME::";

    Class<?> value();

    Class<?> with();

    String beanName() default DEFAULT_BEAN_NAME;

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface ReplaceWithImpls {
        ReplaceWithImpl[] value();
    }

    public static class ReplaceWithImplAnnotationVisitor implements AnnotationVisitor<ReplaceWithImpl> {

        @Override
        public AnnotationVisitorResult visitClass(ReplaceWithImpl annotation) {
            Class<?> reject = annotation.value();
            Class<?> with = annotation.with();
            if (reject == null || with == null) {
                throw new IllegalArgumentException("Both the class to replace, and the class to replace with "
                        + "must be set for the ReplaceWithImpl annotation to work.");
            }
            String beanName = getBeanName(annotation, reject.getName() + "_ReplaceWithImpl_" + with.getName());
            return new AnnotationVisitorResult()
                    .addSpringBeanDefiner(new RegularDefinition(with, beanName))
// Path: src/main/java/no/saua/remock/internal/AnnotationVisitor.java
// public interface AnnotationVisitor<T extends Annotation> {
//     AnnotationVisitorResult visitClass(T annotation);
// 
//     AnnotationVisitorResult visitField(T annotation, Field field);
// 
//     public static class AnnotationVisitorResult {
//         private Set<SpringBeanDefiner> definers = new HashSet<>();
//         private Set<SpyDefinition> spies = new HashSet<>();
//         private Set<Rejecter> rejecters = new HashSet<>();
// 
//         public AnnotationVisitorResult addSpringBeanDefiner(SpringBeanDefiner springBeanDefiner) {
//             definers.add(springBeanDefiner);
//             return this;
//         }
// 
//         public AnnotationVisitorResult addSpy(SpyDefinition spyDefinition) {
//             spies.add(spyDefinition);
//             return this;
//         }
// 
//         public AnnotationVisitorResult addRejecter(Rejecter rejecter) {
//             rejecters.add(rejecter);
//             return this;
//         }
// 
//         Set<SpringBeanDefiner> getDefiners() {
//             return definers;
//         }
// 
//         Set<SpyDefinition> getSpies() {
//             return spies;
//         }
// 
//         Set<Rejecter> getRejecters() {
//             return rejecters;
//         }
//     }
// }
// 
// Path: src/main/java/no/saua/remock/internal/RegularDefinition.java
// public class RegularDefinition extends Entity<RegularDefinition> implements SpringBeanDefiner {
// 
//     private final Class<?> beanClass;
//     private final String beanName;
// 
//     public RegularDefinition(Class<?> beanClass, String beanName) {
//         this.beanClass = beanClass;
//         this.beanName = beanName;
//     }
// 
//     @Override
//     public BeanDefinition getBeanDefinition() {
//         GenericBeanDefinition beanDef = new GenericBeanDefinition();
//         beanDef.setBeanClass(beanClass);
//         return beanDef;
//     }
// 
//     @Override
//     public String getBeanName() {
//         return beanName;
//     }
// 
//     @Override
//     public int hashCode() {
//         return Objects.hash(beanClass, beanName);
//     }
// 
//     @Override
//     public boolean equals(RegularDefinition other) {
//         return Objects.equals(beanClass, other.beanClass) && Objects.equals(beanName, other.beanName);
//     }
// }
// 
// Path: src/main/java/no/saua/remock/internal/RejectBeanClassDefinition.java
// public class RejectBeanClassDefinition extends Entity<RejectBeanClassDefinition> implements Rejecter {
// 
//     private final Class<?> rejectClass;
//     private final List<Class<?>> exceptClasses;
// 
//     public RejectBeanClassDefinition(Class<?> rejectClass) {
//         this(rejectClass, Collections.<Class<?>>emptyList());
//     }
// 
//     public RejectBeanClassDefinition(Class<?> rejectClass, List<Class<?>> exceptClasses) {
//         this.rejectClass = Objects.requireNonNull(rejectClass);
//         this.exceptClasses = Objects.requireNonNull(exceptClasses);
// 
//     }
// 
//     @Override
//     public boolean shouldReject(String beanName, Class<?> beanClass) {
//         if (beanClass == null || !rejectClass.isAssignableFrom(beanClass)) {
//             // -> Class does not match
//             return false;
//         }
// 
//         // E-> Class matches
//         for (Class<?> clazz : exceptClasses) {
//             if (clazz.isAssignableFrom(beanClass)) {
//                 return false;
//             }
//         }
// 
//         return true;
//     }
// 
//     @Override
//     public boolean equals(RejectBeanClassDefinition other) {
//         return Objects.equals(rejectClass, other.rejectClass) && Objects.equals(exceptClasses, other.exceptClasses);
//     }
// 
//     @Override
//     public int hashCode() {
//         return Objects.hash(rejectClass, exceptClasses);
//     }
// }


// Path: src/main/java/no/saua/remock/ReplaceWithImpl.java
import no.saua.remock.internal.AnnotationVisitor;
import no.saua.remock.internal.RegularDefinition;
import no.saua.remock.internal.RejectBeanClassDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import java.lang.annotation.*;
import java.lang.reflect.Field;

package no.saua.remock;



/**
 * Rejects any class with the class {@link #value()} and replaces it with a {@link #with()}. Useful
 * if you want to actually implement a complete mock class. Note that this annotation also is a
 * meta-annotation for {@link Autowired} causing the annotated field to automatically be injected.
 * Usage:
 * <p>
 * <pre>
 * &#064;RunWith(SpringJUnit4ClassRunner.class)
 * &#064;BootstrapWith(RemockBootstrapper.class)
 * &#064;ContextConfiguration(classes = {ServiceImpl.class})
 * public static class ReplaceWithImplAnnotatedOnFieldTest {
 *
 *     &#064;ReplaceWithImpl(value = ServiceImpl.class, with = ServiceMock.class)
 *     public Service service;
 *
 *     &#064;Test
 *     public void test() {
 *         assertEquals(ServiceMock.class, service.getClass());
 *     }
 * }
 * </pre>
 */
@Autowired
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(ReplaceWithImpl.ReplaceWithImpls.class)
public @interface ReplaceWithImpl {

    public static final String DEFAULT_BEAN_NAME = "::REMOCK_DEFAULT_BEAN_NAME::";

    Class<?> value();

    Class<?> with();

    String beanName() default DEFAULT_BEAN_NAME;

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface ReplaceWithImpls {
        ReplaceWithImpl[] value();
    }

    public static class ReplaceWithImplAnnotationVisitor implements AnnotationVisitor<ReplaceWithImpl> {

        @Override
        public AnnotationVisitorResult visitClass(ReplaceWithImpl annotation) {
            Class<?> reject = annotation.value();
            Class<?> with = annotation.with();
            if (reject == null || with == null) {
                throw new IllegalArgumentException("Both the class to replace, and the class to replace with "
                        + "must be set for the ReplaceWithImpl annotation to work.");
            }
            String beanName = getBeanName(annotation, reject.getName() + "_ReplaceWithImpl_" + with.getName());
            return new AnnotationVisitorResult()
                    .addSpringBeanDefiner(new RegularDefinition(with, beanName))
                    .addRejecter(new RejectBeanClassDefinition(reject));