/**
  * 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;
 }
 private void createKeyConstructor() {
   annotatedClass.addConstructor(
       ACC_PUBLIC,
       params(param(STRING_TYPE, "key")),
       NO_EXCEPTIONS,
       block(
           ASTHelper.isDSLObject(annotatedClass.getSuperClass())
               ? ctorSuperS(args("key"))
               : ctorSuperS(),
           assignS(propX(varX("this"), keyField.getName()), varX("key"))));
 }
 boolean makeGetField(
     final Expression receiver,
     final ClassNode receiverType,
     final String fieldName,
     final boolean implicitThis,
     final boolean samePackage) {
   FieldNode field = receiverType.getField(fieldName);
   // direct access is allowed if we are in the same class as the declaring class
   // or we are in an inner class
   if (field != null && isDirectAccessAllowed(field, controller.getClassNode(), samePackage)) {
     CompileStack compileStack = controller.getCompileStack();
     MethodVisitor mv = controller.getMethodVisitor();
     if (field.isStatic()) {
       mv.visitFieldInsn(
           GETSTATIC,
           BytecodeHelper.getClassInternalName(field.getOwner()),
           fieldName,
           BytecodeHelper.getTypeDescription(field.getOriginType()));
       controller.getOperandStack().push(field.getOriginType());
     } else {
       if (implicitThis) {
         compileStack.pushImplicitThis(implicitThis);
       }
       receiver.visit(controller.getAcg());
       if (implicitThis) compileStack.popImplicitThis();
       if (!controller.getOperandStack().getTopOperand().isDerivedFrom(field.getOwner())) {
         mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(field.getOwner()));
       }
       mv.visitFieldInsn(
           GETFIELD,
           BytecodeHelper.getClassInternalName(field.getOwner()),
           fieldName,
           BytecodeHelper.getTypeDescription(field.getOriginType()));
     }
     controller.getOperandStack().replace(field.getOriginType());
     return true;
   }
   ClassNode superClass = receiverType.getSuperClass();
   if (superClass != null) {
     return makeGetField(receiver, superClass, fieldName, implicitThis, false);
   }
   return false;
 }
  private void createValidateMethod() {
    Validation.Mode mode =
        getEnumMemberValue(
            getAnnotation(annotatedClass, VALIDATION_ANNOTATION),
            "mode",
            Validation.Mode.class,
            Validation.Mode.AUTOMATIC);

    annotatedClass.addField(
        "$manualValidation",
        ACC_PRIVATE,
        ClassHelper.Boolean_TYPE,
        new ConstantExpression(mode == Validation.Mode.MANUAL));
    MethodBuilder.createPublicMethod("manualValidation")
        .param(Boolean_TYPE, "validation")
        .assignS(varX("$manualValidation"), varX("validation"))
        .addTo(annotatedClass);

    MethodBuilder methodBuilder = MethodBuilder.createPublicMethod(VALIDATE_METHOD);

    if (ASTHelper.isDSLObject(annotatedClass.getSuperClass())) {
      methodBuilder.statement(callSuperX(VALIDATE_METHOD));
    }

    BlockStatement block = new BlockStatement();
    validateFields(block);
    validateCustomMethods(block);

    TryCatchStatement tryCatchStatement = new TryCatchStatement(block, EmptyStatement.INSTANCE);
    tryCatchStatement.addCatch(
        new CatchStatement(
            param(ASSERTION_ERROR_TYPE, "e"),
            new ThrowStatement(
                ctorX(VALIDATION_EXCEPTION_TYPE, args(propX(varX("e"), "message"), varX("e"))))));
    tryCatchStatement.addCatch(
        new CatchStatement(
            param(EXCEPTION_TYPE, "e"),
            new ThrowStatement(
                ctorX(VALIDATION_EXCEPTION_TYPE, args(propX(varX("e"), "message"), varX("e"))))));

    methodBuilder.statement(tryCatchStatement).addTo(annotatedClass);
  }
  private boolean trySubscript(
      final Expression receiver,
      final String message,
      final Expression arguments,
      ClassNode rType,
      final ClassNode aType) {
    if (getWrapper(rType).isDerivedFrom(Number_TYPE)
        && getWrapper(aType).isDerivedFrom(Number_TYPE)) {
      if ("plus".equals(message)
          || "minus".equals(message)
          || "multiply".equals(message)
          || "div".equals(message)) {
        writeNumberNumberCall(receiver, message, arguments);
        return true;
      } else if ("power".equals(message)) {
        writePowerCall(receiver, arguments, rType, aType);
        return true;
      } else if ("mod".equals(message)) {
        writeModCall(receiver, arguments, rType, aType);
        return true;
      }
    } else if (STRING_TYPE.equals(rType) && "plus".equals(message)) {
      writeStringPlusCall(receiver, message, arguments);
      return true;
    } else if ("getAt".equals(message)) {
      if (rType.isArray() && getWrapper(aType).isDerivedFrom(Number_TYPE)) {
        writeArrayGet(receiver, arguments, rType, aType);
        return true;
      } else {
        // check if a getAt method can be found on the receiver
        ClassNode current = rType;
        MethodNode getAtNode = null;
        while (current != null && getAtNode == null) {
          getAtNode = current.getMethod("getAt", new Parameter[] {new Parameter(aType, "index")});
          if (getAtNode == null && isPrimitiveType(aType)) {
            getAtNode =
                current.getMethod(
                    "getAt", new Parameter[] {new Parameter(getWrapper(aType), "index")});
          } else if (getAtNode == null && aType.isDerivedFrom(Number_TYPE)) {
            getAtNode =
                current.getMethod(
                    "getAt", new Parameter[] {new Parameter(getUnwrapper(aType), "index")});
          }
          current = current.getSuperClass();
        }
        if (getAtNode != null) {
          MethodCallExpression call = new MethodCallExpression(receiver, "getAt", arguments);
          call.setSourcePosition(arguments);
          call.setImplicitThis(false);
          call.setMethodTarget(getAtNode);
          call.visit(controller.getAcg());
          return true;
        }

        // make sure Map#getAt() and List#getAt handled with the bracket syntax are properly
        // compiled
        ClassNode[] args = {aType};
        boolean acceptAnyMethod =
            MAP_TYPE.equals(rType)
                || rType.implementsInterface(MAP_TYPE)
                || LIST_TYPE.equals(rType)
                || rType.implementsInterface(LIST_TYPE);
        List<MethodNode> nodes =
            StaticTypeCheckingSupport.findDGMMethodsByNameAndArguments(
                controller.getSourceUnit().getClassLoader(), rType, message, args);
        if (nodes.isEmpty()) {
          // retry with raw types
          rType = rType.getPlainNodeReference();
          nodes =
              StaticTypeCheckingSupport.findDGMMethodsByNameAndArguments(
                  controller.getSourceUnit().getClassLoader(), rType, message, args);
        }
        nodes = StaticTypeCheckingSupport.chooseBestMethod(rType, nodes, args);
        if (nodes.size() == 1 || nodes.size() > 1 && acceptAnyMethod) {
          MethodNode methodNode = nodes.get(0);
          MethodCallExpression call = new MethodCallExpression(receiver, message, arguments);
          call.setSourcePosition(arguments);
          call.setImplicitThis(false);
          call.setMethodTarget(methodNode);
          call.visit(controller.getAcg());
          return true;
        }
        if (implementsInterfaceOrIsSubclassOf(rType, MAP_TYPE)) {
          // fallback to Map#get
          MethodCallExpression call = new MethodCallExpression(receiver, "get", arguments);
          call.setMethodTarget(MAP_GET_METHOD);
          call.setSourcePosition(arguments);
          call.setImplicitThis(false);
          call.visit(controller.getAcg());
          return true;
        }
      }
    }
    return false;
  }
  private boolean makeGetPropertyWithGetter(
      final Expression receiver,
      final ClassNode receiverType,
      final String methodName,
      final boolean safe,
      final boolean implicitThis) {
    // does a getter exists ?
    String getterName = "get" + MetaClassHelper.capitalize(methodName);
    MethodNode getterNode = receiverType.getGetterMethod(getterName);
    if (getterNode == null) {
      getterName = "is" + MetaClassHelper.capitalize(methodName);
      getterNode = receiverType.getGetterMethod(getterName);
    }
    if (getterNode != null
        && receiver instanceof ClassExpression
        && !CLASS_Type.equals(receiverType)
        && !getterNode.isStatic()) {
      return false;
    }

    // GROOVY-5561: if two files are compiled in the same source unit
    // and that one references the other, the getters for properties have not been
    // generated by the compiler yet (generated by the Verifier)
    PropertyNode propertyNode = receiverType.getProperty(methodName);
    if (propertyNode != null) {
      // it is possible to use a getter
      String prefix = "get";
      if (boolean_TYPE.equals(propertyNode.getOriginType())) {
        prefix = "is";
      }
      getterName = prefix + MetaClassHelper.capitalize(methodName);
      getterNode =
          new MethodNode(
              getterName,
              ACC_PUBLIC,
              propertyNode.getOriginType(),
              Parameter.EMPTY_ARRAY,
              ClassNode.EMPTY_ARRAY,
              EmptyStatement.INSTANCE);
      getterNode.setDeclaringClass(receiverType);
      if (propertyNode.isStatic()) getterNode.setModifiers(ACC_PUBLIC + ACC_STATIC);
    }
    if (getterNode != null) {
      MethodCallExpression call =
          new MethodCallExpression(receiver, getterName, ArgumentListExpression.EMPTY_ARGUMENTS);
      call.setSourcePosition(receiver);
      call.setMethodTarget(getterNode);
      call.setImplicitThis(implicitThis);
      call.setSafe(safe);
      call.visit(controller.getAcg());
      return true;
    }

    if (receiverType instanceof InnerClassNode && !receiverType.isStaticClass()) {
      if (makeGetPropertyWithGetter(
          receiver, receiverType.getOuterClass(), methodName, safe, implicitThis)) {
        return true;
      }
    }

    // go upper level
    ClassNode superClass = receiverType.getSuperClass();
    if (superClass != null) {
      return makeGetPropertyWithGetter(receiver, superClass, methodName, safe, implicitThis);
    }
    return false;
  }