protected void injectDomainHandler(
      ClassNode classNode, String implementation, String datasource) {
    classNode.addMethod(
        new MethodNode(
            DOMAIN_HANDLER_METHOD_NAME,
            Modifier.PUBLIC | Modifier.STATIC,
            GRIFFON_DOMAIN_HANDLER_CLASS,
            Parameter.EMPTY_ARRAY,
            ClassNode.EMPTY_ARRAY,
            returns(domainHandlerInstance())));

    classNode.addMethod(
        new MethodNode(
            MAPPING,
            Modifier.PUBLIC | Modifier.STATIC,
            ClassHelper.STRING_TYPE,
            Parameter.EMPTY_ARRAY,
            ClassNode.EMPTY_ARRAY,
            returns(constx(implementation))));

    classNode.addMethod(
        new MethodNode(
            DATASOURCE,
            Modifier.PUBLIC | Modifier.STATIC,
            ClassHelper.STRING_TYPE,
            Parameter.EMPTY_ARRAY,
            ClassNode.EMPTY_ARRAY,
            returns(constx(datasource))));
  }
  protected void injectMethodMissing(ClassNode classNode) {
    FieldNode methodMissingInterceptor =
        classNode.addField(
            "this$methodMissingInterceptor",
            Modifier.FINAL | Modifier.STATIC | Modifier.PRIVATE | ACC_SYNTHETIC,
            METHOD_MISSING_INTERCEPTOR_CLASS,
            ctor(
                METHOD_MISSING_INTERCEPTOR_CLASS,
                args(classx(classNode), domainHandlerInstance())));

    classNode.addMethod(
        new MethodNode(
            "$static_methodMissing",
            Modifier.PUBLIC | Modifier.STATIC,
            ClassHelper.OBJECT_TYPE,
            params(
                param(ClassHelper.STRING_TYPE, "methodName"),
                param(ClassHelper.makeWithoutCaching(Object[].class), "arguments")),
            ClassNode.EMPTY_ARRAY,
            returns(
                call(
                    field(methodMissingInterceptor),
                    "handleMethodMissing",
                    args(var("methodName"), var("arguments"))))));
  }
 /**
  * Snoops through the declaring class and all parents looking for methods
  *
  * <ul>
  *   <li><code>public String getMessage(java.lang.String)</code>
  *   <li><code>public String getMessage(java.lang.String, java.util.Locale)</code>
  *   <li><code>public String getMessage(java.lang.String, java.lang.Object[])</code>
  *   <li><code>public String getMessage(java.lang.String, java.lang.Object[], java.util.Locale)
  *       </code>
  *   <li><code>public String getMessage(java.lang.String, java.util.List)</code>
  *   <li><code>public String getMessage(java.lang.String, java.util.List, java.util.Locale)</code>
  *   <li><code>public String getMessage(java.lang.String, java.util.Map)</code>
  *   <li><code>public String getMessage(java.lang.String, java.util.Map, java.util.Locale)</code>
  *   <li><code>public String getMessage(java.lang.String, java.lang.String)</code>
  *   <li><code>public String getMessage(java.lang.String, java.lang.String, java.util.Locale)
  *       </code>
  *   <li><code>public String getMessage(java.lang.String, java.lang.Object[], java.lang.String)
  *       </code>
  *   <li><code>
  *       public String getMessage(java.lang.String, java.lang.Object[], java.lang.String, java.util.Locale)
  *       </code>
  *   <li><code>public String getMessage(java.lang.String, java.util.List, java.lang.String)</code>
  *   <li><code>
  *       public String getMessage(java.lang.String, java.util.List, java.lang.String, java.util.Locale)
  *       </code>
  *   <li><code>public String getMessage(java.lang.String, java.util.Map, java.lang.String)</code>
  *   <li><code>
  *       public String getMessage(java.lang.String, java.util.Map, java.lang.String, java.util.Locale)
  *       </code>
  * </ul>
  *
  * If any are defined all must be defined or a compilation error results.
  *
  * @param declaringClass the class to search
  * @param sourceUnit the source unit, for error reporting. {@code @NotNull}.
  * @return true if property change support should be added
  */
 protected static boolean needsMessageSource(ClassNode declaringClass, SourceUnit sourceUnit) {
   boolean found = false;
   ClassNode consideredClass = declaringClass;
   while (consideredClass != null) {
     for (MethodNode method : consideredClass.getMethods()) {
       // just check length, MOP will match it up
       found = method.getName().equals(METHOD_GET_MESSAGE) && method.getParameters().length == 1;
       found |= method.getName().equals(METHOD_GET_MESSAGE) && method.getParameters().length == 2;
       found |= method.getName().equals(METHOD_GET_MESSAGE) && method.getParameters().length == 3;
       found |= method.getName().equals(METHOD_GET_MESSAGE) && method.getParameters().length == 4;
       if (found) return false;
     }
     consideredClass = consideredClass.getSuperClass();
   }
   if (found) {
     sourceUnit
         .getErrorCollector()
         .addErrorAndContinue(
             new SimpleMessage(
                 "@MessageSourceAware cannot be processed on "
                     + declaringClass.getName()
                     + " because some but not all of variants of getMessage() were declared in the current class or super classes.",
                 sourceUnit));
     return false;
   }
   return true;
 }
 /**
  * Convenience method to see if an annotated node is {@code @MybatisAware}.
  *
  * @param node the node to check
  * @return true if the node is an event publisher
  */
 public static boolean hasMybatisAwareAnnotation(AnnotatedNode node) {
   for (AnnotationNode annotation : node.getAnnotations()) {
     if (MYBATIS_AWARE_CNODE.equals(annotation.getClassNode())) {
       return true;
     }
   }
   return false;
 }
 public static void addResourceLocatorIfNeeded(SourceUnit source, ClassNode classNode) {
   if (needsMessageSource(classNode, source)) {
     if (LOG.isDebugEnabled()) {
       LOG.debug("Injecting " + ResourceLocator.class.getName() + " into " + classNode.getName());
     }
     apply(classNode);
   }
 }
 /**
  * Convenience method to see if an annotated node is {@code @MessageSourceAware}.
  *
  * @param node the node to check
  * @return true if the node is an event publisher
  */
 public static boolean hasMessageSourceAwareAnnotation(AnnotatedNode node) {
   for (AnnotationNode annotation : node.getAnnotations()) {
     if (MESSAGE_SOURCE_AWARE_TYPE.equals(annotation.getClassNode())) {
       return true;
     }
   }
   return false;
 }
 public static void addMybatisContributionIfNeeded(SourceUnit source, ClassNode classNode) {
   if (needsMybatisContribution(classNode, source)) {
     if (LOG.isDebugEnabled()) {
       LOG.debug(
           "Injecting "
               + MybatisContributionHandler.class.getName()
               + " into "
               + classNode.getName());
     }
     apply(classNode);
   }
 }
 protected static boolean needsMybatisContribution(
     ClassNode declaringClass, SourceUnit sourceUnit) {
   boolean found1 = false, found2 = false, found3 = false, found4 = false;
   ClassNode consideredClass = declaringClass;
   while (consideredClass != null) {
     for (MethodNode method : consideredClass.getMethods()) {
       // just check length, MOP will match it up
       found1 =
           method.getName().equals(METHOD_WITH_SQL_SESSION) && method.getParameters().length == 1;
       found2 =
           method.getName().equals(METHOD_WITH_SQL_SESSION) && method.getParameters().length == 2;
       found3 =
           method.getName().equals(METHOD_SET_MYBATIS_PROVIDER)
               && method.getParameters().length == 1;
       found4 =
           method.getName().equals(METHOD_GET_MYBATIS_PROVIDER)
               && method.getParameters().length == 0;
       if (found1 && found2 && found3 && found4) {
         return false;
       }
     }
     consideredClass = consideredClass.getSuperClass();
   }
   if (found1 || found2 || found3 || found4) {
     sourceUnit
         .getErrorCollector()
         .addErrorAndContinue(
             new SimpleMessage(
                 "@MybatisAware cannot be processed on "
                     + declaringClass.getName()
                     + " because some but not all of methods from "
                     + MybatisContributionHandler.class.getName()
                     + " were declared in the current class or super classes.",
                 sourceUnit));
     return false;
   }
   return true;
 }
  protected void injectMethod(ClassNode classNode, MethodSignature methodSignature) {
    Parameter[] parameters = makeParameters(methodSignature.getParameterTypes());

    int modifiers = Modifier.PUBLIC;
    if (methodSignature.isStatic()) modifiers |= Modifier.STATIC;
    String returnTypeClassName = methodSignature.getReturnType().getName();
    ClassNode returnType =
        GRIFFON_DOMAIN_CLASSNAME.equals(returnTypeClassName)
            ? classNode
            : ClassHelper.makeWithoutCaching(methodSignature.getReturnType());
    classNode.addMethod(
        new MethodNode(
            methodSignature.getMethodName(),
            modifiers,
            returnType,
            parameters,
            ClassNode.EMPTY_ARRAY,
            makeMethodBody(classNode, methodSignature, parameters)));
  }
  public static void apply(ClassNode declaringClass) {
    injectInterface(declaringClass, MYBATIS_CONTRIBUTION_HANDLER_CNODE);

    // add field:
    // protected MybatisProvider this$MybatisProvider = DefaultMybatisProvider.instance
    FieldNode providerField =
        declaringClass.addField(
            MYBATIS_PROVIDER_FIELD_NAME,
            ACC_PRIVATE | ACC_SYNTHETIC,
            MYBATIS_PROVIDER_CNODE,
            defaultMybatisProviderInstance());

    // add method:
    // MybatisProvider getMybatisProvider() {
    //     return this$MybatisProvider
    // }
    injectMethod(
        declaringClass,
        new MethodNode(
            METHOD_GET_MYBATIS_PROVIDER,
            ACC_PUBLIC,
            MYBATIS_PROVIDER_CNODE,
            Parameter.EMPTY_ARRAY,
            NO_EXCEPTIONS,
            returns(field(providerField))));

    // add method:
    // void setMybatisProvider(MybatisProvider provider) {
    //     this$MybatisProvider = provider ?: DefaultMybatisProvider.instance
    // }
    injectMethod(
        declaringClass,
        new MethodNode(
            METHOD_SET_MYBATIS_PROVIDER,
            ACC_PUBLIC,
            ClassHelper.VOID_TYPE,
            params(param(MYBATIS_PROVIDER_CNODE, PROVIDER)),
            NO_EXCEPTIONS,
            block(
                ifs_no_return(
                    cmp(var(PROVIDER), ConstantExpression.NULL),
                    assigns(field(providerField), defaultMybatisProviderInstance()),
                    assigns(field(providerField), var(PROVIDER))))));

    for (MethodNode method : MYBATIS_CONTRIBUTION_HANDLER_CNODE.getMethods()) {
      if (Arrays.binarySearch(DELEGATING_METHODS, method.getName()) < 0) continue;
      List<Expression> variables = new ArrayList<Expression>();
      Parameter[] parameters = new Parameter[method.getParameters().length];
      for (int i = 0; i < method.getParameters().length; i++) {
        Parameter p = method.getParameters()[i];
        parameters[i] = new Parameter(makeClassSafe(p.getType()), p.getName());
        parameters[i].getType().setGenericsTypes(p.getType().getGenericsTypes());
        variables.add(var(p.getName()));
      }
      ClassNode returnType = makeClassSafe(method.getReturnType());
      returnType.setGenericsTypes(method.getReturnType().getGenericsTypes());
      returnType.setGenericsPlaceHolder(method.getReturnType().isGenericsPlaceHolder());

      MethodNode newMethod =
          new MethodNode(
              method.getName(),
              ACC_PUBLIC,
              returnType,
              parameters,
              NO_EXCEPTIONS,
              returns(call(field(providerField), method.getName(), args(variables))));
      newMethod.setGenericsTypes(method.getGenericsTypes());
      injectMethod(declaringClass, newMethod);
    }
  }