/**
  * 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;
 }
 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);
   }
 }
  private FieldNode getKeyField(ClassNode target) {

    List<FieldNode> annotatedFields = getAnnotatedFieldsOfHierarchy(target, KEY_ANNOTATION);

    if (annotatedFields.isEmpty()) return null;

    if (annotatedFields.size() > 1) {
      addCompileError(
          String.format(
              "Found more than one key fields, only one is allowed in hierarchy (%s, %s)",
              getQualifiedName(annotatedFields.get(0)), getQualifiedName(annotatedFields.get(1))),
          annotatedFields.get(0));
      return null;
    }

    FieldNode result = annotatedFields.get(0);

    if (!result.getType().equals(ClassHelper.STRING_TYPE)) {
      addCompileError(
          String.format(
              "Key field '%s' must be of type String, but is '%s' instead",
              result.getName(), result.getType().getName()),
          result);
      return null;
    }

    ClassNode ancestor = ASTHelper.getHighestAncestorDSLObject(target);

    if (target.equals(ancestor)) return result;

    FieldNode firstKey = getKeyField(ancestor);

    if (firstKey == null) {
      addCompileError(
          String.format(
              "Inconsistent hierarchy: Toplevel class %s has no key, but child class %s defines '%s'.",
              ancestor.getName(), target.getName(), result.getName()),
          result);
      return null;
    }

    return result;
  }
  private void createMapOfDSLObjectMethods(
      FieldNode fieldNode, ClassNode keyType, ClassNode elementType) {
    if (getKeyField(elementType) == null) {
      addCompileError(
          String.format(
              "Value type of map %s (%s) has no key field",
              fieldNode.getName(), elementType.getName()),
          fieldNode);
      return;
    }

    MethodBuilder.createPublicMethod(fieldNode.getName())
        .closureParam("closure")
        .assignS(propX(varX("closure"), "delegate"), varX("this"))
        .assignS(
            propX(varX("closure"), "resolveStrategy"),
            propX(classX(ClassHelper.CLOSURE_TYPE), "DELEGATE_FIRST"))
        .callMethod("closure", "call")
        .addTo(annotatedClass);

    String methodName = getElementNameForCollectionField(fieldNode);
    String targetOwner = getOwnerFieldName(elementType);

    if (!ASTHelper.isAbstract(elementType)) {
      MethodBuilder.createPublicMethod(methodName)
          .returning(elementType)
          .namedParams("values")
          .param(keyType, "key")
          .delegatingClosureParam(elementType)
          .declareVariable("created", callX(classX(elementType), "newInstance", args("key")))
          .callMethod("created", "copyFromTemplate")
          .optionalAssignThisToPropertyS("created", targetOwner, targetOwner)
          .callMethod(fieldNode.getName(), "put", args(varX("key"), varX("created")))
          .callMethod("created", "apply", args("values", "closure"))
          .callValidationOn("created")
          .doReturn("created")
          .addTo(annotatedClass);
      MethodBuilder.createPublicMethod(methodName)
          .returning(elementType)
          .param(keyType, "key")
          .delegatingClosureParam(elementType)
          .declareVariable("created", callX(classX(elementType), "newInstance", args("key")))
          .callMethod("created", "copyFromTemplate")
          .optionalAssignThisToPropertyS("created", targetOwner, targetOwner)
          .callMethod(fieldNode.getName(), "put", args(varX("key"), varX("created")))
          .callMethod("created", "apply", varX("closure"))
          .callValidationOn("created")
          .doReturn("created")
          .addTo(annotatedClass);
    }

    if (!isFinal(elementType)) {
      MethodBuilder.createPublicMethod(methodName)
          .returning(elementType)
          .namedParams("values")
          .classParam("typeToCreate", elementType)
          .param(keyType, "key")
          .delegatingClosureParam(elementType)
          .declareVariable("created", callX(varX("typeToCreate"), "newInstance", args("key")))
          .callMethod("created", "copyFromTemplate")
          .callMethod(fieldNode.getName(), "put", args(varX("key"), varX("created")))
          .optionalAssignThisToPropertyS("created", targetOwner, targetOwner)
          .callMethod("created", "apply", args("values", "closure"))
          .callValidationOn("created")
          .doReturn("created")
          .addTo(annotatedClass);
      MethodBuilder.createPublicMethod(methodName)
          .returning(elementType)
          .classParam("typeToCreate", elementType)
          .param(keyType, "key")
          .delegatingClosureParam(elementType)
          .declareVariable("created", callX(varX("typeToCreate"), "newInstance", args("key")))
          .callMethod("created", "copyFromTemplate")
          .callMethod(fieldNode.getName(), "put", args(varX("key"), varX("created")))
          .optionalAssignThisToPropertyS("created", targetOwner, targetOwner)
          .callMethod("created", "apply", varX("closure"))
          .callValidationOn("created")
          .doReturn("created")
          .addTo(annotatedClass);
    }

    //noinspection ConstantConditions
    MethodBuilder.createPublicMethod(methodName)
        .param(elementType, "value")
        .callMethod(
            fieldNode.getName(),
            "put",
            args(propX(varX("value"), getKeyField(elementType).getName()), varX("value")))
        .optionalAssignThisToPropertyS("value", targetOwner, targetOwner)
        .addTo(annotatedClass);
  }