/**
  * 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;
 }
 @SuppressWarnings("unchecked")
 private boolean makeGetPrivateFieldWithBridgeMethod(
     final Expression receiver,
     final ClassNode receiverType,
     final String fieldName,
     final boolean safe,
     final boolean implicitThis) {
   FieldNode field = receiverType.getField(fieldName);
   ClassNode classNode = controller.getClassNode();
   if (field != null
       && Modifier.isPrivate(field.getModifiers())
       && (StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(receiverType, classNode)
           || StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(classNode, receiverType))
       && !receiverType.equals(classNode)) {
     Map<String, MethodNode> accessors =
         (Map<String, MethodNode>)
             receiverType
                 .redirect()
                 .getNodeMetaData(StaticCompilationMetadataKeys.PRIVATE_FIELDS_ACCESSORS);
     if (accessors != null) {
       MethodNode methodNode = accessors.get(fieldName);
       if (methodNode != null) {
         MethodCallExpression mce =
             new MethodCallExpression(
                 receiver,
                 methodNode.getName(),
                 new ArgumentListExpression(
                     field.isStatic() ? new ConstantExpression(null) : receiver));
         mce.setMethodTarget(methodNode);
         mce.setSafe(safe);
         mce.setImplicitThis(implicitThis);
         mce.visit(controller.getAcg());
         return true;
       }
     }
   }
   return false;
 }
  @Override
  public void makeGetPropertySite(
      Expression receiver,
      final String methodName,
      final boolean safe,
      final boolean implicitThis) {
    Object dynamic =
        receiver.getNodeMetaData(StaticCompilationMetadataKeys.RECEIVER_OF_DYNAMIC_PROPERTY);
    if (dynamic != null) {
      MethodNode target =
          safe ? INVOKERHELPER_GETPROPERTYSAFE_METHOD : INVOKERHELPER_GETPROPERTY_METHOD;
      MethodCallExpression mce =
          new MethodCallExpression(
              new ClassExpression(INVOKERHELPER_TYPE),
              target.getName(),
              new ArgumentListExpression(receiver, new ConstantExpression(methodName)));
      mce.setSafe(false);
      mce.setImplicitThis(false);
      mce.setMethodTarget(target);
      mce.visit(controller.getAcg());
      return;
    }
    TypeChooser typeChooser = controller.getTypeChooser();
    ClassNode classNode = controller.getClassNode();
    ClassNode receiverType =
        (ClassNode) receiver.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER);
    if (receiverType == null) {
      receiverType = typeChooser.resolveType(receiver, classNode);
    }
    Object type = receiver.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE);
    if (type == null && receiver instanceof VariableExpression) {
      Variable variable = ((VariableExpression) receiver).getAccessedVariable();
      if (variable instanceof Expression) {
        type = ((Expression) variable).getNodeMetaData(StaticTypesMarker.INFERRED_TYPE);
      }
    }
    if (type != null) {
      // in case a "flow type" is found, it is preferred to use it instead of
      // the declaration type
      receiverType = (ClassNode) type;
    }
    boolean isClassReceiver = false;
    if (isClassClassNodeWrappingConcreteType(receiverType)) {
      isClassReceiver = true;
      receiverType = receiverType.getGenericsTypes()[0].getType();
    }
    MethodVisitor mv = controller.getMethodVisitor();

    if (receiverType.isArray() && methodName.equals("length")) {
      receiver.visit(controller.getAcg());
      ClassNode arrayGetReturnType = typeChooser.resolveType(receiver, classNode);
      controller.getOperandStack().doGroovyCast(arrayGetReturnType);
      mv.visitInsn(ARRAYLENGTH);
      controller.getOperandStack().replace(int_TYPE);
      return;
    } else if ((receiverType.implementsInterface(COLLECTION_TYPE)
            || COLLECTION_TYPE.equals(receiverType))
        && ("size".equals(methodName) || "length".equals(methodName))) {
      MethodCallExpression expr =
          new MethodCallExpression(receiver, "size", ArgumentListExpression.EMPTY_ARGUMENTS);
      expr.setMethodTarget(COLLECTION_SIZE_METHOD);
      expr.setImplicitThis(implicitThis);
      expr.setSafe(safe);
      expr.visit(controller.getAcg());
      return;
    }
    if (makeGetPropertyWithGetter(receiver, receiverType, methodName, safe, implicitThis)) return;
    if (makeGetField(
        receiver,
        receiverType,
        methodName,
        implicitThis,
        samePackages(receiverType.getPackageName(), classNode.getPackageName()))) return;
    if (receiverType.isEnum()) {
      mv.visitFieldInsn(
          GETSTATIC,
          BytecodeHelper.getClassInternalName(receiverType),
          methodName,
          BytecodeHelper.getTypeDescription(receiverType));
      controller.getOperandStack().push(receiverType);
      return;
    }
    if (receiver instanceof ClassExpression) {
      if (makeGetField(
          receiver,
          receiver.getType(),
          methodName,
          implicitThis,
          samePackages(receiver.getType().getPackageName(), classNode.getPackageName()))) return;
      if (makeGetPropertyWithGetter(receiver, receiver.getType(), methodName, safe, implicitThis))
        return;
      if (makeGetPrivateFieldWithBridgeMethod(
          receiver, receiver.getType(), methodName, safe, implicitThis)) return;
    }
    if (isClassReceiver) {
      // we are probably looking for a property of the class
      if (makeGetPropertyWithGetter(receiver, CLASS_Type, methodName, safe, implicitThis)) return;
      if (makeGetField(receiver, CLASS_Type, methodName, false, true)) return;
    }
    if (makeGetPrivateFieldWithBridgeMethod(receiver, receiverType, methodName, safe, implicitThis))
      return;

    // GROOVY-5580, it is still possible that we're calling a superinterface property
    String getterName = "get" + MetaClassHelper.capitalize(methodName);
    if (receiverType.isInterface()) {
      Set<ClassNode> allInterfaces = receiverType.getAllInterfaces();
      MethodNode getterMethod = null;
      for (ClassNode anInterface : allInterfaces) {
        getterMethod = anInterface.getGetterMethod(getterName);
        if (getterMethod != null) break;
      }
      // GROOVY-5585
      if (getterMethod == null) {
        getterMethod = OBJECT_TYPE.getGetterMethod(getterName);
      }

      if (getterMethod != null) {
        MethodCallExpression call =
            new MethodCallExpression(receiver, getterName, ArgumentListExpression.EMPTY_ARGUMENTS);
        call.setMethodTarget(getterMethod);
        call.setImplicitThis(false);
        call.setSourcePosition(receiver);
        call.visit(controller.getAcg());
        return;
      }
    }

    // GROOVY-5568, we would be facing a DGM call, but instead of foo.getText(), have foo.text
    List<MethodNode> methods =
        findDGMMethodsByNameAndArguments(
            controller.getSourceUnit().getClassLoader(),
            receiverType,
            getterName,
            ClassNode.EMPTY_ARRAY);
    if (!methods.isEmpty()) {
      List<MethodNode> methodNodes = chooseBestMethod(receiverType, methods, ClassNode.EMPTY_ARRAY);
      if (methodNodes.size() == 1) {
        MethodNode getter = methodNodes.get(0);
        MethodCallExpression call =
            new MethodCallExpression(receiver, getterName, ArgumentListExpression.EMPTY_ARGUMENTS);
        call.setMethodTarget(getter);
        call.setImplicitThis(false);
        call.setSourcePosition(receiver);
        call.visit(controller.getAcg());
        return;
      }
    }

    boolean isStaticProperty =
        receiver instanceof ClassExpression
            && (receiverType.isDerivedFrom(receiver.getType())
                || receiverType.implementsInterface(receiver.getType()));

    if (!isStaticProperty) {
      if (receiverType.implementsInterface(MAP_TYPE) || MAP_TYPE.equals(receiverType)) {
        // for maps, replace map.foo with map.get('foo')
        writeMapDotProperty(receiver, methodName, mv, safe);
        return;
      }
      if (receiverType.implementsInterface(LIST_TYPE) || LIST_TYPE.equals(receiverType)) {
        writeListDotProperty(receiver, methodName, mv, safe);
        return;
      }
    }

    controller
        .getSourceUnit()
        .addError(
            new SyntaxException(
                "Access to "
                    + (receiver instanceof ClassExpression ? receiver.getType() : receiverType)
                        .toString(false)
                    + "#"
                    + methodName
                    + " is forbidden",
                receiver.getLineNumber(),
                receiver.getColumnNumber(),
                receiver.getLastLineNumber(),
                receiver.getLastColumnNumber()));
    controller.getMethodVisitor().visitInsn(ACONST_NULL);
    controller.getOperandStack().push(OBJECT_TYPE);
  }
  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;
  }