private void assertMembersNamesAreUnique() {
    Map<String, FieldNode> allDslCollectionFieldNodesOfHierarchy = new HashMap<String, FieldNode>();

    for (ClassNode level : ASTHelper.getHierarchyOfDSLObjectAncestors(annotatedClass)) {
      for (FieldNode field : level.getFields()) {
        if (!ASTHelper.isListOrMap(field.getType())) continue;

        String memberName = getElementNameForCollectionField(field);

        FieldNode conflictingField = allDslCollectionFieldNodesOfHierarchy.get(memberName);

        if (conflictingField != null) {
          addCompileError(
              String.format(
                  "Member name %s is used more than once: %s:%s and %s:%s",
                  memberName,
                  field.getOwner().getName(),
                  field.getName(),
                  conflictingField.getOwner().getName(),
                  conflictingField.getName()),
              field);
          return;
        }

        allDslCollectionFieldNodesOfHierarchy.put(memberName, field);
      }
    }
  }
 /**
  * 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"))));
 }
 /**
  * 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;
 }
  private List<FieldNode> getAnnotatedFieldOfClass(ClassNode target, ClassNode annotation) {
    List<FieldNode> result = new ArrayList<FieldNode>();

    for (FieldNode fieldNode : target.getFields())
      if (!fieldNode.getAnnotations(annotation).isEmpty()) result.add(fieldNode);

    return result;
  }
 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;
  }
 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 void validateFields(BlockStatement block) {
    Validation.Option mode =
        getEnumMemberValue(
            getAnnotation(annotatedClass, VALIDATION_ANNOTATION),
            "option",
            Validation.Option.class,
            Validation.Option.IGNORE_UNMARKED);
    for (FieldNode fieldNode : annotatedClass.getFields()) {
      if (shouldFieldBeIgnoredForValidation(fieldNode)) continue;

      ClosureExpression validationClosure =
          createGroovyTruthClosureExpression(block.getVariableScope());
      String message = null;

      AnnotationNode validateAnnotation = getAnnotation(fieldNode, VALIDATE_ANNOTATION);
      if (validateAnnotation != null) {
        message =
            getMemberStringValue(
                validateAnnotation, "message", "'" + fieldNode.getName() + "' must be set!");
        Expression member = validateAnnotation.getMember("value");
        if (member instanceof ClassExpression) {
          ClassNode memberType = member.getType();
          if (memberType.equals(ClassHelper.make(Validate.Ignore.class))) continue;
          else if (!memberType.equals(ClassHelper.make(Validate.GroovyTruth.class))) {
            addError(
                "value of Validate must be either Validate.GroovyTruth, Validate.Ignore or a closure.",
                fieldNode);
          }
        } else if (member instanceof ClosureExpression) {
          validationClosure = (ClosureExpression) member;
        }
      }

      if (validateAnnotation != null || mode == Validation.Option.VALIDATE_UNMARKED) {
        block.addStatement(
            new AssertStatement(
                new BooleanExpression(
                    callX(validationClosure, "call", args(varX(fieldNode.getName())))),
                message == null ? ConstantExpression.NULL : new ConstantExpression(message)));
      }
    }
  }
 @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;
 }
 private void writeArrayGet(
     final Expression receiver,
     final Expression arguments,
     final ClassNode rType,
     final ClassNode aType) {
   OperandStack operandStack = controller.getOperandStack();
   int m1 = operandStack.getStackLength();
   // visit receiver
   receiver.visit(controller.getAcg());
   // visit arguments as array index
   arguments.visit(controller.getAcg());
   operandStack.doGroovyCast(int_TYPE);
   int m2 = operandStack.getStackLength();
   // array access
   controller.getMethodVisitor().visitInsn(AALOAD);
   operandStack.replace(rType.getComponentType(), m2 - m1);
 }
  private void validateFieldAnnotations() {
    for (FieldNode fieldNode : annotatedClass.getFields()) {
      if (shouldFieldBeIgnored(fieldNode)) continue;

      AnnotationNode annotation = getAnnotation(fieldNode, DSL_FIELD_ANNOTATION);

      if (annotation == null) continue;

      if (ASTHelper.isListOrMap(fieldNode.getType())) return;

      if (annotation.getMember("members") != null) {
        addCompileError(
            String.format(
                "@Field.members is only valid for List or Map fields, but field %s is of type %s",
                fieldNode.getName(), fieldNode.getType().getName()),
            annotation);
      }
    }
  }
  private static boolean isDirectAccessAllowed(
      FieldNode a, ClassNode receiver, boolean isSamePackage) {
    ClassNode declaringClass = a.getDeclaringClass().redirect();
    ClassNode receiverType = receiver.redirect();

    // first, direct access from within the class or inner class nodes
    if (declaringClass.equals(receiverType)) return true;
    if (receiverType instanceof InnerClassNode) {
      while (receiverType != null && receiverType instanceof InnerClassNode) {
        if (declaringClass.equals(receiverType)) return true;
        receiverType = receiverType.getOuterClass();
      }
    }

    // no getter
    return a.isPublic() || (a.isProtected() && isSamePackage);
  }
  @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 void createFieldMethods() {
   for (FieldNode fieldNode : annotatedClass.getFields()) createMethodsForSingleField(fieldNode);
 }
  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;
  }
  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);
  }
/**
 * A call site writer which replaces call site caching with static calls. This means that the
 * generated code looks more like Java code than dynamic Groovy code. Best effort is made to use JVM
 * instructions instead of calls to helper methods.
 *
 * @author Cedric Champeau
 */
public class StaticTypesCallSiteWriter extends CallSiteWriter implements Opcodes {

  private static final ClassNode INVOKERHELPER_TYPE = ClassHelper.make(InvokerHelper.class);
  private static final MethodNode GROOVYOBJECT_GETPROPERTY_METHOD =
      GROOVY_OBJECT_TYPE.getMethod(
          "getProperty", new Parameter[] {new Parameter(STRING_TYPE, "propertyName")});
  private static final MethodNode INVOKERHELPER_GETPROPERTY_METHOD =
      INVOKERHELPER_TYPE.getMethod(
          "getProperty",
          new Parameter[] {
            new Parameter(OBJECT_TYPE, "object"), new Parameter(STRING_TYPE, "propertyName")
          });
  private static final MethodNode INVOKERHELPER_GETPROPERTYSAFE_METHOD =
      INVOKERHELPER_TYPE.getMethod(
          "getPropertySafe",
          new Parameter[] {
            new Parameter(OBJECT_TYPE, "object"), new Parameter(STRING_TYPE, "propertyName")
          });
  private static final ClassNode COLLECTION_TYPE = make(Collection.class);
  private static final MethodNode COLLECTION_SIZE_METHOD =
      COLLECTION_TYPE.getMethod("size", Parameter.EMPTY_ARRAY);
  private static final MethodNode MAP_GET_METHOD =
      MAP_TYPE.getMethod("get", new Parameter[] {new Parameter(OBJECT_TYPE, "key")});

  private WriterController controller;

  public StaticTypesCallSiteWriter(final StaticTypesWriterController controller) {
    super(controller);
    this.controller = controller;
  }

  @Override
  public void generateCallSiteArray() {
    if (controller instanceof StaticTypesWriterController) {
      ((StaticTypesWriterController) controller).getRegularCallSiteWriter().generateCallSiteArray();
    } else {
      super.generateCallSiteArray();
    }
  }

  @Override
  public void makeCallSite(
      final Expression receiver,
      final String message,
      final Expression arguments,
      final boolean safe,
      final boolean implicitThis,
      final boolean callCurrent,
      final boolean callStatic) {}

  @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 void writeMapDotProperty(
      final Expression receiver,
      final String methodName,
      final MethodVisitor mv,
      final boolean safe) {
    receiver.visit(controller.getAcg()); // load receiver

    Label exit = new Label();
    if (safe) {
      Label doGet = new Label();
      mv.visitJumpInsn(IFNONNULL, doGet);
      controller.getOperandStack().remove(1);
      mv.visitInsn(ACONST_NULL);
      mv.visitJumpInsn(GOTO, exit);
      mv.visitLabel(doGet);
      receiver.visit(controller.getAcg());
    }

    mv.visitLdcInsn(methodName); // load property name
    mv.visitMethodInsn(
        INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", true);
    if (safe) {
      mv.visitLabel(exit);
    }
    controller.getOperandStack().replace(OBJECT_TYPE);
  }

  private void writeListDotProperty(
      final Expression receiver,
      final String methodName,
      final MethodVisitor mv,
      final boolean safe) {
    ClassNode componentType =
        (ClassNode) receiver.getNodeMetaData(StaticCompilationMetadataKeys.COMPONENT_TYPE);
    if (componentType == null) {
      componentType = OBJECT_TYPE;
    }
    // for lists, replace list.foo with:
    // def result = new ArrayList(list.size())
    // for (e in list) { result.add (e.foo) }
    // result
    CompileStack compileStack = controller.getCompileStack();

    Label exit = new Label();
    if (safe) {
      receiver.visit(controller.getAcg());
      Label doGet = new Label();
      mv.visitJumpInsn(IFNONNULL, doGet);
      controller.getOperandStack().remove(1);
      mv.visitInsn(ACONST_NULL);
      mv.visitJumpInsn(GOTO, exit);
      mv.visitLabel(doGet);
    }

    Variable tmpList = new VariableExpression("tmpList", make(ArrayList.class));
    int var = compileStack.defineTemporaryVariable(tmpList, false);
    Variable iterator = new VariableExpression("iterator", Iterator_TYPE);
    int it = compileStack.defineTemporaryVariable(iterator, false);
    Variable nextVar = new VariableExpression("next", componentType);
    final int next = compileStack.defineTemporaryVariable(nextVar, false);

    mv.visitTypeInsn(NEW, "java/util/ArrayList");
    mv.visitInsn(DUP);
    receiver.visit(controller.getAcg());
    mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "size", "()I", true);
    controller.getOperandStack().remove(1);
    mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "(I)V", false);
    mv.visitVarInsn(ASTORE, var);
    Label l1 = new Label();
    mv.visitLabel(l1);
    receiver.visit(controller.getAcg());
    mv.visitMethodInsn(
        INVOKEINTERFACE, "java/util/List", "iterator", "()Ljava/util/Iterator;", true);
    controller.getOperandStack().remove(1);
    mv.visitVarInsn(ASTORE, it);
    Label l2 = new Label();
    mv.visitLabel(l2);
    mv.visitVarInsn(ALOAD, it);
    mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "hasNext", "()Z", true);
    Label l3 = new Label();
    mv.visitJumpInsn(IFEQ, l3);
    mv.visitVarInsn(ALOAD, it);
    mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "next", "()Ljava/lang/Object;", true);
    mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(componentType));
    mv.visitVarInsn(ASTORE, next);
    Label l4 = new Label();
    mv.visitLabel(l4);
    mv.visitVarInsn(ALOAD, var);
    final ClassNode finalComponentType = componentType;
    PropertyExpression pexp =
        new PropertyExpression(
            new BytecodeExpression() {
              @Override
              public void visit(final MethodVisitor mv) {
                mv.visitVarInsn(ALOAD, next);
              }

              @Override
              public ClassNode getType() {
                return finalComponentType;
              }
            },
            methodName);
    pexp.visit(controller.getAcg());
    controller.getOperandStack().box();
    controller.getOperandStack().remove(1);
    mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true);
    mv.visitInsn(POP);
    Label l5 = new Label();
    mv.visitLabel(l5);
    mv.visitJumpInsn(GOTO, l2);
    mv.visitLabel(l3);
    mv.visitVarInsn(ALOAD, var);
    if (safe) {
      mv.visitLabel(exit);
    }
    controller.getOperandStack().push(make(ArrayList.class));
    controller.getCompileStack().removeVar(next);
    controller.getCompileStack().removeVar(it);
    controller.getCompileStack().removeVar(var);
  }

  @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 makeGroovyObjectGetPropertySite(
      final Expression receiver,
      final String methodName,
      final boolean safe,
      final boolean implicitThis) {
    TypeChooser typeChooser = controller.getTypeChooser();
    ClassNode classNode = controller.getClassNode();
    ClassNode receiverType = typeChooser.resolveType(receiver, classNode);
    if (receiver instanceof VariableExpression
        && ((VariableExpression) receiver).isThisExpression()
        && !controller.isInClosure()) {
      receiverType = classNode;
    }

    String property = methodName;
    if (implicitThis) {
      if (controller.getInvocationWriter() instanceof StaticInvocationWriter) {
        MethodCallExpression currentCall =
            ((StaticInvocationWriter) controller.getInvocationWriter()).getCurrentCall();
        if (currentCall != null
            && currentCall.getNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER) != null) {
          property = (String) currentCall.getNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER);
          String[] props = property.split("\\.");
          BytecodeExpression thisLoader =
              new BytecodeExpression() {
                @Override
                public void visit(final MethodVisitor mv) {
                  mv.visitVarInsn(ALOAD, 0); // load this
                }
              };
          thisLoader.setType(CLOSURE_TYPE);
          Expression pexp =
              new PropertyExpression(thisLoader, new ConstantExpression(props[0]), safe);
          for (int i = 1, propsLength = props.length; i < propsLength; i++) {
            final String prop = props[i];
            pexp.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, CLOSURE_TYPE);
            pexp = new PropertyExpression(pexp, prop);
          }
          pexp.visit(controller.getAcg());
          return;
        }
      }
    }

    if (makeGetPropertyWithGetter(receiver, receiverType, property, safe, implicitThis)) return;
    if (makeGetField(
        receiver,
        receiverType,
        property,
        implicitThis,
        samePackages(receiverType.getPackageName(), classNode.getPackageName()))) return;

    MethodCallExpression call =
        new MethodCallExpression(
            receiver, "getProperty", new ArgumentListExpression(new ConstantExpression(property)));
    call.setImplicitThis(implicitThis);
    call.setSafe(safe);
    call.setMethodTarget(GROOVYOBJECT_GETPROPERTY_METHOD);
    call.visit(controller.getAcg());
    return;
  }

  @Override
  public void makeCallSiteArrayInitializer() {}

  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;
  }

  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 static boolean samePackages(final String pkg1, final String pkg2) {
    return ((pkg1 == null && pkg2 == null) || pkg1 != null && pkg1.equals(pkg2));
  }

  private static boolean isDirectAccessAllowed(
      FieldNode a, ClassNode receiver, boolean isSamePackage) {
    ClassNode declaringClass = a.getDeclaringClass().redirect();
    ClassNode receiverType = receiver.redirect();

    // first, direct access from within the class or inner class nodes
    if (declaringClass.equals(receiverType)) return true;
    if (receiverType instanceof InnerClassNode) {
      while (receiverType != null && receiverType instanceof InnerClassNode) {
        if (declaringClass.equals(receiverType)) return true;
        receiverType = receiverType.getOuterClass();
      }
    }

    // no getter
    return a.isPublic() || (a.isProtected() && isSamePackage);
  }

  @Override
  public void makeSiteEntry() {}

  @Override
  public void prepareCallSite(final String message) {}

  @Override
  public void makeSingleArgumentCall(
      final Expression receiver, final String message, final Expression arguments) {
    TypeChooser typeChooser = controller.getTypeChooser();
    ClassNode classNode = controller.getClassNode();
    ClassNode rType = typeChooser.resolveType(receiver, classNode);
    ClassNode aType = typeChooser.resolveType(arguments, classNode);
    if (trySubscript(receiver, message, arguments, rType, aType)) {
      return;
    }
    // new try with flow type instead of declaration type
    rType = receiver.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE);
    if (rType != null && trySubscript(receiver, message, arguments, rType, aType)) {
      return;
    }
    // todo: more cases
    throw new GroovyBugError(
        "At line "
            + receiver.getLineNumber()
            + " column "
            + receiver.getColumnNumber()
            + "\n"
            + "On receiver: "
            + receiver.getText()
            + " with message: "
            + message
            + " and arguments: "
            + arguments.getText()
            + "\n"
            + "This method should not have been called. Please try to create a simple example reproducing this error and file"
            + "a bug report at http://jira.codehaus.org/browse/GROOVY");
  }

  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 void writeArrayGet(
      final Expression receiver,
      final Expression arguments,
      final ClassNode rType,
      final ClassNode aType) {
    OperandStack operandStack = controller.getOperandStack();
    int m1 = operandStack.getStackLength();
    // visit receiver
    receiver.visit(controller.getAcg());
    // visit arguments as array index
    arguments.visit(controller.getAcg());
    operandStack.doGroovyCast(int_TYPE);
    int m2 = operandStack.getStackLength();
    // array access
    controller.getMethodVisitor().visitInsn(AALOAD);
    operandStack.replace(rType.getComponentType(), m2 - m1);
  }

  private void writeModCall(
      Expression receiver, Expression arguments, ClassNode rType, ClassNode aType) {
    prepareSiteAndReceiver(receiver, "mod", false, controller.getCompileStack().isLHS());
    controller.getOperandStack().doGroovyCast(Number_TYPE);
    visitBoxedArgument(arguments);
    controller.getOperandStack().doGroovyCast(Number_TYPE);
    MethodVisitor mv = controller.getMethodVisitor();
    mv.visitMethodInsn(
        INVOKESTATIC,
        "org/codehaus/groovy/runtime/typehandling/NumberMath",
        "mod",
        "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;",
        false);
    controller.getOperandStack().replace(Number_TYPE, 2);
  }

  private void writePowerCall(
      Expression receiver, Expression arguments, final ClassNode rType, ClassNode aType) {
    OperandStack operandStack = controller.getOperandStack();
    int m1 = operandStack.getStackLength();
    // slow Path
    prepareSiteAndReceiver(receiver, "power", false, controller.getCompileStack().isLHS());
    operandStack.doGroovyCast(getWrapper(rType));
    visitBoxedArgument(arguments);
    operandStack.doGroovyCast(getWrapper(aType));
    int m2 = operandStack.getStackLength();
    MethodVisitor mv = controller.getMethodVisitor();
    if (BigDecimal_TYPE.equals(rType) && Integer_TYPE.equals(getWrapper(aType))) {
      mv.visitMethodInsn(
          INVOKESTATIC,
          "org/codehaus/groovy/runtime/DefaultGroovyMethods",
          "power",
          "(Ljava/math/BigDecimal;Ljava/lang/Integer;)Ljava/lang/Number;",
          false);
    } else if (BigInteger_TYPE.equals(rType) && Integer_TYPE.equals(getWrapper(aType))) {
      mv.visitMethodInsn(
          INVOKESTATIC,
          "org/codehaus/groovy/runtime/DefaultGroovyMethods",
          "power",
          "(Ljava/math/BigInteger;Ljava/lang/Integer;)Ljava/lang/Number;",
          false);
    } else if (Long_TYPE.equals(getWrapper(rType)) && Integer_TYPE.equals(getWrapper(aType))) {
      mv.visitMethodInsn(
          INVOKESTATIC,
          "org/codehaus/groovy/runtime/DefaultGroovyMethods",
          "power",
          "(Ljava/lang/Long;Ljava/lang/Integer;)Ljava/lang/Number;",
          false);
    } else if (Integer_TYPE.equals(getWrapper(rType)) && Integer_TYPE.equals(getWrapper(aType))) {
      mv.visitMethodInsn(
          INVOKESTATIC,
          "org/codehaus/groovy/runtime/DefaultGroovyMethods",
          "power",
          "(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Number;",
          false);
    } else {
      mv.visitMethodInsn(
          INVOKESTATIC,
          "org/codehaus/groovy/runtime/DefaultGroovyMethods",
          "power",
          "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;",
          false);
    }
    controller.getOperandStack().replace(Number_TYPE, m2 - m1);
  }

  private void writeStringPlusCall(
      final Expression receiver, final String message, final Expression arguments) {
    // todo: performance would be better if we created a StringBuilder
    OperandStack operandStack = controller.getOperandStack();
    int m1 = operandStack.getStackLength();
    // slow Path
    prepareSiteAndReceiver(receiver, message, false, controller.getCompileStack().isLHS());
    visitBoxedArgument(arguments);
    int m2 = operandStack.getStackLength();
    MethodVisitor mv = controller.getMethodVisitor();
    mv.visitMethodInsn(
        INVOKESTATIC,
        "org/codehaus/groovy/runtime/DefaultGroovyMethods",
        "plus",
        "(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;",
        false);
    controller.getOperandStack().replace(STRING_TYPE, m2 - m1);
  }

  private void writeNumberNumberCall(
      final Expression receiver, final String message, final Expression arguments) {
    OperandStack operandStack = controller.getOperandStack();
    int m1 = operandStack.getStackLength();
    // slow Path
    prepareSiteAndReceiver(receiver, message, false, controller.getCompileStack().isLHS());
    controller.getOperandStack().doGroovyCast(Number_TYPE);
    visitBoxedArgument(arguments);
    controller.getOperandStack().doGroovyCast(Number_TYPE);
    int m2 = operandStack.getStackLength();
    MethodVisitor mv = controller.getMethodVisitor();
    mv.visitMethodInsn(
        INVOKESTATIC,
        "org/codehaus/groovy/runtime/dgmimpl/NumberNumber" + MetaClassHelper.capitalize(message),
        message,
        "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;",
        false);
    controller.getOperandStack().replace(Number_TYPE, m2 - m1);
  }
}
  @Override
  public void makeGroovyObjectGetPropertySite(
      final Expression receiver,
      final String methodName,
      final boolean safe,
      final boolean implicitThis) {
    TypeChooser typeChooser = controller.getTypeChooser();
    ClassNode classNode = controller.getClassNode();
    ClassNode receiverType = typeChooser.resolveType(receiver, classNode);
    if (receiver instanceof VariableExpression
        && ((VariableExpression) receiver).isThisExpression()
        && !controller.isInClosure()) {
      receiverType = classNode;
    }

    String property = methodName;
    if (implicitThis) {
      if (controller.getInvocationWriter() instanceof StaticInvocationWriter) {
        MethodCallExpression currentCall =
            ((StaticInvocationWriter) controller.getInvocationWriter()).getCurrentCall();
        if (currentCall != null
            && currentCall.getNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER) != null) {
          property = (String) currentCall.getNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER);
          String[] props = property.split("\\.");
          BytecodeExpression thisLoader =
              new BytecodeExpression() {
                @Override
                public void visit(final MethodVisitor mv) {
                  mv.visitVarInsn(ALOAD, 0); // load this
                }
              };
          thisLoader.setType(CLOSURE_TYPE);
          Expression pexp =
              new PropertyExpression(thisLoader, new ConstantExpression(props[0]), safe);
          for (int i = 1, propsLength = props.length; i < propsLength; i++) {
            final String prop = props[i];
            pexp.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, CLOSURE_TYPE);
            pexp = new PropertyExpression(pexp, prop);
          }
          pexp.visit(controller.getAcg());
          return;
        }
      }
    }

    if (makeGetPropertyWithGetter(receiver, receiverType, property, safe, implicitThis)) return;
    if (makeGetField(
        receiver,
        receiverType,
        property,
        implicitThis,
        samePackages(receiverType.getPackageName(), classNode.getPackageName()))) return;

    MethodCallExpression call =
        new MethodCallExpression(
            receiver, "getProperty", new ArgumentListExpression(new ConstantExpression(property)));
    call.setImplicitThis(implicitThis);
    call.setSafe(safe);
    call.setMethodTarget(GROOVYOBJECT_GETPROPERTY_METHOD);
    call.visit(controller.getAcg());
    return;
  }
 private boolean annotedClassIsTopOfDSLHierarchy() {
   return ownerField != null && annotatedClass.getDeclaredField(ownerField.getName()) != null;
 }
 @SuppressWarnings("BooleanMethodIsAlwaysInverted")
 private boolean isFinal(ClassNode classNode) {
   return (classNode.getModifiers() & ACC_FINAL) != 0;
 }
  private void validateCustomMethods(BlockStatement block) {
    if (!annotatedClass.hasMethod("doValidate", Parameter.EMPTY_ARRAY)) return;

    block.addStatement(stmt(callX(varX("this"), "doValidate")));
  }