content: /**
   * 
   */
  package org.activejpa.enhancer;
  
  import java.io.Serializable;
  import java.lang.instrument.ClassFileTransformer;
  import java.lang.instrument.Instrumentation;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Set;
  
  import javax.persistence.Entity;
  
  import org.activejpa.entity.Filter;
  import org.activejpa.entity.Model;
  import org.activejpa.entity.ModelInterceptor;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import net.bytebuddy.agent.builder.AgentBuilder;
  import net.bytebuddy.description.modifier.Ownership;
  import net.bytebuddy.description.modifier.Visibility;
  import net.bytebuddy.description.type.TypeDefinition;
  import net.bytebuddy.description.type.TypeDescription;
  import net.bytebuddy.description.type.TypeDescription.ForLoadedType;
  import net.bytebuddy.description.type.TypeDescription.Generic.OfParameterizedType.ForGenerifiedErasure;
  import net.bytebuddy.dynamic.DynamicType.Builder;
  import net.bytebuddy.dynamic.TargetType;
  import net.bytebuddy.implementation.MethodDelegation;
  import net.bytebuddy.matcher.ElementMatchers;
  import net.bytebuddy.utility.JavaModule;
  
  /**
   * @author ganeshs
   *
   */
  public class ModelClassEnhancer implements AgentBuilder.Transformer {
      
      private Instrumentation instrumentation;
      
      private static Set<String> transformedClasses = new HashSet<>();
  	
      private static final Logger logger = LoggerFactory.getLogger(ModelClassEnhancer.class);
      
      /**
       * Default constructor
       */
      public ModelClassEnhancer() {
      }
      
      /**
       * @param instrumentation
       */
      public ModelClassEnhancer(Instrumentation instrumentation) {
          this.instrumentation = instrumentation;
      }
      
      /**
       * @return
       */
      public ClassFileTransformer getTransformer() {
          logger.info("Creating a class file transformer");
          AgentBuilder builder = new AgentBuilder.Default()
                  .with(AgentBuilder.PoolStrategy.Default.FAST)
                  .type(ElementMatchers.isSubTypeOf(Model.class).and(ElementMatchers.isAnnotatedWith(Entity.class)))
                  .transform(this);
          return instrumentation != null ? builder.installOn(instrumentation) : builder.installOnByteBuddyAgent();
      }
      
      @Override
      public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
          logger.info("Transforming the model class - {}", typeDescription.getActualName());
          if (transformedClasses.contains(typeDescription.getActualName())) {
              logger.info("Model class - {} is already transformed. Skipping", typeDescription.getActualName());
              return builder;
          }
          
          builder = defineMethod(builder, typeDescription, classLoader, "all", TargetType.resolve(new ForLoadedType(List.class), typeDescription));
          builder = defineMethod(builder, typeDescription, classLoader, "count", new ForLoadedType(long.class));
          builder = defineMethod(builder, typeDescription, classLoader, "count", new ForLoadedType(long.class), new TypeDescription.ForLoadedType(Filter.class));
          builder = defineMethod(builder, typeDescription, classLoader, "findById", ForGenerifiedErasure.of(new ForLoadedType(Model.class)), new ForLoadedType(Serializable.class));
          builder = defineMethod(builder, typeDescription, classLoader, "deleteAll", TypeDescription.VOID, new TypeDescription.ForLoadedType(Filter.class));
          builder = defineMethod(builder, typeDescription, classLoader, "deleteAll", TypeDescription.VOID);
          builder = defineMethod(builder, typeDescription, classLoader, "exists", new ForLoadedType(boolean.class), new ForLoadedType(Serializable.class));
          builder = defineMethod(builder, typeDescription, classLoader, "where", TargetType.resolve(new ForLoadedType(List.class), typeDescription), new ForLoadedType(Object[].class));
          builder = defineMethod(builder, typeDescription, classLoader, "where", TargetType.resolve(new ForLoadedType(List.class), typeDescription), new ForLoadedType(Filter.class));
          builder = defineMethod(builder, typeDescription, classLoader, "one", ForGenerifiedErasure.of(new ForLoadedType(Model.class)), new ForLoadedType(Object[].class));
          builder = defineMethod(builder, typeDescription, classLoader, "first", ForGenerifiedErasure.of(new ForLoadedType(Model.class)), new ForLoadedType(Object[].class));
          builder = defineMethod(builder, typeDescription, classLoader, "filter", new ForLoadedType(Filter.class));
          transformedClasses.add(typeDescription.getActualName());
          return builder;
      }
      
      /**
       * @param builder
       * @param typeDescription
       * @param classLoader
       * @param name
       * @param targetType
       * @param parameters
       * @return
       */
      private Builder<?> defineMethod(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, String name, TypeDefinition targetType, TypeDefinition... parameters) {
          logger.info("Defining the method - {}.{} with return type - {} and parameters - {}", typeDescription.getActualName(), name, targetType.getActualName(), parameters);
          try {
              builder = builder.defineMethod(name, targetType, Ownership.STATIC, Visibility.PUBLIC).withParameters(parameters).intercept(MethodDelegation.to(ModelInterceptor.class));
              builder.make();
          } catch (Exception exception) {
              if (! (exception.getCause() instanceof NoSuchMethodException)) {
                  logger.error("Failed while defining the method - {}.{}", typeDescription.getActualName(), name, exception);
                  throw new AssertionError(exception);
              }
          }
          return builder;
      }
  }
  