Example #1
0
 private ClassNode configureClass(Class c) {
   if (c.isPrimitive()) {
     return ClassHelper.make(c);
   } else {
     return ClassHelper.makeWithoutCaching(c, false);
   }
 }
Example #2
0
 private ClassNode configureTypeVariableReference(TypeVariable tv) {
   ClassNode cn = ClassHelper.makeWithoutCaching(tv.getName());
   cn.setGenericsPlaceHolder(true);
   ClassNode cn2 = ClassHelper.makeWithoutCaching(tv.getName());
   cn2.setGenericsPlaceHolder(true);
   GenericsType[] gts = new GenericsType[] {new GenericsType(cn2)};
   cn.setGenericsTypes(gts);
   cn.setRedirect(ClassHelper.OBJECT_TYPE);
   return cn;
 }
  protected void injectMethodMissing(ClassNode classNode) {
    FieldNode methodMissingInterceptor =
        classNode.addField(
            "this$methodMissingInterceptor",
            Modifier.FINAL | Modifier.STATIC | Modifier.PRIVATE | ACC_SYNTHETIC,
            METHOD_MISSING_INTERCEPTOR_CLASS,
            ctor(
                METHOD_MISSING_INTERCEPTOR_CLASS,
                args(classx(classNode), domainHandlerInstance())));

    classNode.addMethod(
        new MethodNode(
            "$static_methodMissing",
            Modifier.PUBLIC | Modifier.STATIC,
            ClassHelper.OBJECT_TYPE,
            params(
                param(ClassHelper.STRING_TYPE, "methodName"),
                param(ClassHelper.makeWithoutCaching(Object[].class), "arguments")),
            ClassNode.EMPTY_ARRAY,
            returns(
                call(
                    field(methodMissingInterceptor),
                    "handleMethodMissing",
                    args(var("methodName"), var("arguments"))))));
  }
Example #4
0
 private void setAnnotationMetaData(Annotation[] annotations, AnnotatedNode an) {
   for (Annotation annotation : annotations) {
     AnnotationNode node = new AnnotationNode(ClassHelper.make(annotation.annotationType()));
     configureAnnotation(node, annotation);
     an.addAnnotation(node);
   }
 }
 protected Parameter[] makeParameters(Class[] parameterTypes) {
   if (parameterTypes.length == 0) return Parameter.EMPTY_ARRAY;
   Parameter[] parameters = new Parameter[parameterTypes.length];
   for (int i = 0; i < parameterTypes.length; i++) {
     parameters[i] = new Parameter(ClassHelper.makeWithoutCaching(parameterTypes[i]), "arg" + i);
   }
   return parameters;
 }
 private boolean isKnownImmutableClass(ClassNode fieldType, List<String> knownImmutableClasses) {
   if (!fieldType.isResolved()) return false;
   return fieldType.isEnum()
       || ClassHelper.isPrimitiveType(fieldType)
       || fieldType.getAnnotations(MY_TYPE).size() != 0
       || inImmutableList(fieldType.getName())
       || knownImmutableClasses.contains(fieldType.getName());
 }
Example #7
0
  private ClassNode configureWildcardType(WildcardType wildcardType) {
    ClassNode base = ClassHelper.makeWithoutCaching("?");
    base.setRedirect(ClassHelper.OBJECT_TYPE);
    // TODO: more than one lower bound for wildcards?
    ClassNode[] lowers = configureTypes(wildcardType.getLowerBounds());
    ClassNode lower = null;
    // TODO: is it safe to remove this? What was the original intention?
    if (lower != null) lower = lowers[0];

    ClassNode[] upper = configureTypes(wildcardType.getUpperBounds());
    GenericsType t = new GenericsType(base, upper, lower);
    t.setWildcard(true);

    ClassNode ref = ClassHelper.makeWithoutCaching(Object.class, false);
    ref.setGenericsTypes(new GenericsType[] {t});

    return ref;
  }
Example #8
0
 public ClassNode getPlainNodeReference() {
   if (ClassHelper.isPrimitiveType(this)) return this;
   ClassNode n = new ClassNode(name, modifiers, superClass, null, null);
   n.isPrimaryNode = false;
   n.setRedirect(redirect());
   if (isArray()) {
     n.componentType = redirect().getComponentType();
   }
   return n;
 }
  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)));
      }
    }
  }
Example #10
0
 private ClassNode makeClassNode(CompileUnit cu, Type t, Class c) {
   ClassNode back = null;
   if (cu != null) back = cu.getClass(c.getName());
   if (back == null) back = ClassHelper.make(c);
   if (!(t instanceof Class)) {
     ClassNode front = configureType(t);
     front.setRedirect(back);
     return front;
   }
   return back;
 }
  private Collection createPropertiesForBelongsToExpression(Expression e, ClassNode classNode) {
    List properties = new ArrayList();
    if (e instanceof MapExpression) {
      MapExpression me = (MapExpression) e;
      List mapEntries = me.getMapEntryExpressions();
      for (Iterator i = mapEntries.iterator(); i.hasNext(); ) {
        MapEntryExpression mme = (MapEntryExpression) i.next();
        String key = mme.getKeyExpression().getText();

        String type = mme.getValueExpression().getText();

        properties.add(
            new PropertyNode(
                key, Modifier.PUBLIC, ClassHelper.make(type), classNode, null, null, null));
      }
    }

    return properties;
  }
  protected void injectMethod(ClassNode classNode, MethodSignature methodSignature) {
    Parameter[] parameters = makeParameters(methodSignature.getParameterTypes());

    int modifiers = Modifier.PUBLIC;
    if (methodSignature.isStatic()) modifiers |= Modifier.STATIC;
    String returnTypeClassName = methodSignature.getReturnType().getName();
    ClassNode returnType =
        GRIFFON_DOMAIN_CLASSNAME.equals(returnTypeClassName)
            ? classNode
            : ClassHelper.makeWithoutCaching(methodSignature.getReturnType());
    classNode.addMethod(
        new MethodNode(
            methodSignature.getMethodName(),
            modifiers,
            returnType,
            parameters,
            ClassNode.EMPTY_ARRAY,
            makeMethodBody(classNode, methodSignature, parameters)));
  }
Example #13
0
  private Expression annotationValueToExpression(Object value) {
    if (value == null
        || value instanceof String
        || value instanceof Number
        || value instanceof Character
        || value instanceof Boolean) return new ConstantExpression(value);

    if (value instanceof Class)
      return new ClassExpression(ClassHelper.makeWithoutCaching((Class) value));

    if (value.getClass().isArray()) {
      ListExpression elementExprs = new ListExpression();
      int len = Array.getLength(value);
      for (int i = 0; i != len; ++i)
        elementExprs.addExpression(annotationValueToExpression(Array.get(value, i)));
      return elementExprs;
    }

    return null;
  }
Example #14
0
 private void configureAnnotation(AnnotationNode node, Annotation annotation) {
   Class type = annotation.annotationType();
   if (type == Retention.class) {
     Retention r = (Retention) annotation;
     RetentionPolicy value = r.value();
     setRetentionPolicy(value, node);
     node.setMember(
         "value",
         new PropertyExpression(
             new ClassExpression(ClassHelper.makeWithoutCaching(RetentionPolicy.class, false)),
             value.toString()));
   } else if (type == Target.class) {
     Target t = (Target) annotation;
     ElementType[] elements = t.value();
     ListExpression elementExprs = new ListExpression();
     for (ElementType element : elements) {
       elementExprs.addExpression(
           new PropertyExpression(
               new ClassExpression(ClassHelper.ELEMENT_TYPE_TYPE), element.name()));
     }
     node.setMember("value", elementExprs);
   } else {
     Method[] declaredMethods = type.getDeclaredMethods();
     for (int i = 0; i < declaredMethods.length; i++) {
       Method declaredMethod = declaredMethods[i];
       try {
         Object value = declaredMethod.invoke(annotation);
         Expression valueExpression = annotationValueToExpression(value);
         if (valueExpression == null) continue;
         node.setMember(declaredMethod.getName(), valueExpression);
       } catch (IllegalAccessException e) {
       } catch (InvocationTargetException e) {
       }
     }
   }
 }
/**
 * Handles generation of code for the @Immutable annotation.
 *
 * @author Paul King
 * @author Andre Steingress
 */
@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
public class ImmutableASTTransformation extends AbstractASTTransformation {

  /*
   Currently leaving BigInteger and BigDecimal in list but see:
   http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6348370

   Also, Color is not final so while not normally used with child
   classes, it isn't strictly immutable. Use at your own risk.

   This list can by extended by providing "known immutable" classes
   via Immutable.knownImmutableClasses
  */
  private static List<String> immutableList =
      Arrays.asList(
          "java.lang.Boolean",
          "java.lang.Byte",
          "java.lang.Character",
          "java.lang.Double",
          "java.lang.Float",
          "java.lang.Integer",
          "java.lang.Long",
          "java.lang.Short",
          "java.lang.String",
          "java.math.BigInteger",
          "java.math.BigDecimal",
          "java.awt.Color",
          "java.net.URI",
          "java.util.UUID");
  private static final Class MY_CLASS = groovy.transform.Immutable.class;
  static final ClassNode MY_TYPE = ClassHelper.make(MY_CLASS);
  static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage();
  static final String MEMBER_KNOWN_IMMUTABLE_CLASSES = "knownImmutableClasses";
  static final String MEMBER_KNOWN_IMMUTABLES = "knownImmutables";

  private static final ClassNode DATE_TYPE = ClassHelper.make(Date.class);
  private static final ClassNode CLONEABLE_TYPE = ClassHelper.make(Cloneable.class);
  private static final ClassNode COLLECTION_TYPE =
      ClassHelper.makeWithoutCaching(Collection.class, false);
  private static final ClassNode READONLYEXCEPTION_TYPE =
      ClassHelper.make(ReadOnlyPropertyException.class);
  private static final ClassNode DGM_TYPE = ClassHelper.make(DefaultGroovyMethods.class);
  private static final ClassNode SELF_TYPE = ClassHelper.make(ImmutableASTTransformation.class);
  private static final ClassNode HASHMAP_TYPE =
      ClassHelper.makeWithoutCaching(HashMap.class, false);
  private static final ClassNode MAP_TYPE = ClassHelper.makeWithoutCaching(Map.class, false);
  private static final ClassNode REFLECTION_INVOKER_TYPE =
      ClassHelper.make(ReflectionMethodInvoker.class);
  private static final ClassNode SORTEDSET_CLASSNODE = ClassHelper.make(SortedSet.class);
  private static final ClassNode SORTEDMAP_CLASSNODE = ClassHelper.make(SortedMap.class);
  private static final ClassNode SET_CLASSNODE = ClassHelper.make(Set.class);
  private static final ClassNode MAP_CLASSNODE = ClassHelper.make(Map.class);

  public void visit(ASTNode[] nodes, SourceUnit source) {
    init(nodes, source);
    AnnotatedNode parent = (AnnotatedNode) nodes[1];
    AnnotationNode node = (AnnotationNode) nodes[0];
    // temporarily have weaker check which allows for old Deprecated Annotation
    //        if (!MY_TYPE.equals(node.getClassNode())) return;
    if (!node.getClassNode().getName().endsWith(".Immutable")) return;
    List<PropertyNode> newProperties = new ArrayList<PropertyNode>();

    if (parent instanceof ClassNode) {
      final List<String> knownImmutableClasses = getKnownImmutableClasses(node);
      final List<String> knownImmutables = getKnownImmutables(node);

      ClassNode cNode = (ClassNode) parent;
      String cName = cNode.getName();
      checkNotInterface(cNode, MY_TYPE_NAME);
      makeClassFinal(cNode);

      final List<PropertyNode> pList = getInstanceProperties(cNode);
      for (PropertyNode pNode : pList) {
        adjustPropertyForImmutability(pNode, newProperties);
      }
      for (PropertyNode pNode : newProperties) {
        cNode.getProperties().remove(pNode);
        addProperty(cNode, pNode);
      }
      final List<FieldNode> fList = cNode.getFields();
      for (FieldNode fNode : fList) {
        ensureNotPublic(cName, fNode);
      }
      createConstructors(cNode, knownImmutableClasses, knownImmutables);
      if (!hasAnnotation(cNode, EqualsAndHashCodeASTTransformation.MY_TYPE)) {
        createHashCode(cNode, true, false, false, null, null);
        createEquals(cNode, false, false, false, null, null);
      }
      if (!hasAnnotation(cNode, ToStringASTTransformation.MY_TYPE)) {
        createToString(cNode, false, false, null, null, false, true);
      }
    }
  }

  private void doAddConstructor(final ClassNode cNode, final ConstructorNode constructorNode) {
    cNode.addConstructor(constructorNode);
    // GROOVY-5814: Immutable is not compatible with @CompileStatic
    Parameter argsParam = null;
    for (Parameter p : constructorNode.getParameters()) {
      if ("args".equals(p.getName())) {
        argsParam = p;
        break;
      }
    }
    if (argsParam != null) {
      final Parameter arg = argsParam;
      ClassCodeVisitorSupport variableExpressionFix =
          new ClassCodeVisitorSupport() {
            @Override
            protected SourceUnit getSourceUnit() {
              return cNode.getModule().getContext();
            }

            @Override
            public void visitVariableExpression(final VariableExpression expression) {
              super.visitVariableExpression(expression);
              if ("args".equals(expression.getName())) {
                expression.setAccessedVariable(arg);
              }
            }
          };
      variableExpressionFix.visitConstructor(constructorNode);
    }
  }

  private List<String> getKnownImmutableClasses(AnnotationNode node) {
    final ArrayList<String> immutableClasses = new ArrayList<String>();

    final Expression expression = node.getMember(MEMBER_KNOWN_IMMUTABLE_CLASSES);
    if (expression == null) return immutableClasses;

    if (!(expression instanceof ListExpression)) {
      addError(
          "Use the Groovy list notation [el1, el2] to specify known immutable classes via \""
              + MEMBER_KNOWN_IMMUTABLE_CLASSES
              + "\"",
          node);
      return immutableClasses;
    }

    final ListExpression listExpression = (ListExpression) expression;
    for (Expression listItemExpression : listExpression.getExpressions()) {
      if (listItemExpression instanceof ClassExpression) {
        immutableClasses.add(listItemExpression.getType().getName());
      }
    }

    return immutableClasses;
  }

  private List<String> getKnownImmutables(AnnotationNode node) {
    final ArrayList<String> immutables = new ArrayList<String>();

    final Expression expression = node.getMember(MEMBER_KNOWN_IMMUTABLES);
    if (expression == null) return immutables;

    if (!(expression instanceof ListExpression)) {
      addError(
          "Use the Groovy list notation [el1, el2] to specify known immutable property names via \""
              + MEMBER_KNOWN_IMMUTABLES
              + "\"",
          node);
      return immutables;
    }

    final ListExpression listExpression = (ListExpression) expression;
    for (Expression listItemExpression : listExpression.getExpressions()) {
      if (listItemExpression instanceof ConstantExpression) {
        immutables.add((String) ((ConstantExpression) listItemExpression).getValue());
      }
    }

    return immutables;
  }

  private void makeClassFinal(ClassNode cNode) {
    if ((cNode.getModifiers() & ACC_FINAL) == 0) {
      cNode.setModifiers(cNode.getModifiers() | ACC_FINAL);
    }
  }

  private void createConstructors(
      ClassNode cNode, List<String> knownImmutableClasses, List<String> knownImmutables) {
    if (!validateConstructors(cNode)) return;

    List<PropertyNode> list = getInstanceProperties(cNode);
    boolean specialHashMapCase =
        list.size() == 1 && list.get(0).getField().getType().equals(HASHMAP_TYPE);
    if (specialHashMapCase) {
      createConstructorMapSpecial(cNode, list);
    } else {
      createConstructorMap(cNode, list, knownImmutableClasses, knownImmutables);
      createConstructorOrdered(cNode, list);
    }
  }

  private void createConstructorOrdered(ClassNode cNode, List<PropertyNode> list) {
    final MapExpression argMap = new MapExpression();
    final Parameter[] orderedParams = new Parameter[list.size()];
    int index = 0;
    for (PropertyNode pNode : list) {
      Parameter param = new Parameter(pNode.getField().getType(), pNode.getField().getName());
      orderedParams[index++] = param;
      argMap.addMapEntryExpression(
          new ConstantExpression(pNode.getName()), new VariableExpression(pNode.getName()));
    }
    final BlockStatement orderedBody = new BlockStatement();
    orderedBody.addStatement(
        new ExpressionStatement(
            new ConstructorCallExpression(
                ClassNode.THIS,
                new ArgumentListExpression(new CastExpression(HASHMAP_TYPE, argMap)))));
    doAddConstructor(
        cNode, new ConstructorNode(ACC_PUBLIC, orderedParams, ClassNode.EMPTY_ARRAY, orderedBody));
  }

  private Statement createGetterBodyDefault(FieldNode fNode) {
    final Expression fieldExpr = new VariableExpression(fNode);
    return new ExpressionStatement(fieldExpr);
  }

  private Expression cloneCollectionExpr(Expression fieldExpr) {
    TernaryExpression expression =
        createIfInstanceOfAsImmutableStatement(
            fieldExpr,
            SORTEDSET_CLASSNODE,
            createIfInstanceOfAsImmutableStatement(
                fieldExpr,
                SORTEDMAP_CLASSNODE,
                createIfInstanceOfAsImmutableStatement(
                    fieldExpr,
                    SET_CLASSNODE,
                    createIfInstanceOfAsImmutableStatement(
                        fieldExpr,
                        MAP_CLASSNODE,
                        createIfInstanceOfAsImmutableStatement(
                            fieldExpr,
                            ClassHelper.LIST_TYPE,
                            createAsImmutableExpression(fieldExpr, COLLECTION_TYPE))))));
    return expression;
  }

  private TernaryExpression createIfInstanceOfAsImmutableStatement(
      Expression expr, ClassNode type, Expression elseStatement) {
    return new TernaryExpression(
        new BooleanExpression(
            new BinaryExpression(
                expr,
                Token.newSymbol(Types.KEYWORD_INSTANCEOF, -1, -1),
                new ClassExpression(type))),
        createAsImmutableExpression(expr, type),
        elseStatement);
  }

  private Expression createAsImmutableExpression(final Expression expr, final ClassNode type) {
    return new StaticMethodCallExpression(DGM_TYPE, "asImmutable", new CastExpression(type, expr));
  }

  private Expression cloneArrayOrCloneableExpr(Expression fieldExpr, ClassNode type) {
    StaticMethodCallExpression smce =
        new StaticMethodCallExpression(
            REFLECTION_INVOKER_TYPE,
            "invoke",
            new ArgumentListExpression(
                fieldExpr,
                new ConstantExpression("clone"),
                new ArrayExpression(
                    ClassHelper.OBJECT_TYPE.makeArray(), Collections.<Expression>emptyList())));
    return new CastExpression(type, smce);
  }

  private void createConstructorMapSpecial(ClassNode cNode, List<PropertyNode> list) {
    final BlockStatement body = new BlockStatement();
    body.addStatement(createConstructorStatementMapSpecial(list.get(0).getField()));
    createConstructorMapCommon(cNode, body);
  }

  private void createConstructorMap(
      ClassNode cNode,
      List<PropertyNode> list,
      List<String> knownImmutableClasses,
      List<String> knownImmutables) {
    final BlockStatement body = new BlockStatement();
    for (PropertyNode pNode : list) {
      body.addStatement(
          createConstructorStatement(cNode, pNode, knownImmutableClasses, knownImmutables));
    }
    // check for missing properties
    Expression checkArgs =
        new ArgumentListExpression(new VariableExpression("this"), new VariableExpression("args"));
    body.addStatement(
        new ExpressionStatement(
            new StaticMethodCallExpression(SELF_TYPE, "checkPropNames", checkArgs)));
    createConstructorMapCommon(cNode, body);
  }

  private void createConstructorMapCommon(ClassNode cNode, BlockStatement body) {
    final List<FieldNode> fList = cNode.getFields();
    for (FieldNode fNode : fList) {
      if (fNode.isPublic()) continue; // public fields will be rejected elsewhere
      if (cNode.getProperty(fNode.getName()) != null) continue; // a property
      if (fNode.isFinal() && fNode.isStatic()) continue;
      if (fNode.getName().contains("$")) continue; // internal field
      if (fNode.isFinal() && fNode.getInitialExpression() != null)
        body.addStatement(checkFinalArgNotOverridden(cNode, fNode));
      body.addStatement(createConstructorStatementDefault(fNode));
    }
    final Parameter[] params = new Parameter[] {new Parameter(HASHMAP_TYPE, "args")};
    doAddConstructor(
        cNode,
        new ConstructorNode(
            ACC_PUBLIC,
            params,
            ClassNode.EMPTY_ARRAY,
            new IfStatement(
                equalsNullExpr(new VariableExpression("args")), new EmptyStatement(), body)));
  }

  private Statement checkFinalArgNotOverridden(ClassNode cNode, FieldNode fNode) {
    final String name = fNode.getName();
    Expression value = findArg(name);
    return new IfStatement(
        equalsNullExpr(value),
        new EmptyStatement(),
        new ThrowStatement(
            new ConstructorCallExpression(
                READONLYEXCEPTION_TYPE,
                new ArgumentListExpression(
                    new ConstantExpression(name), new ConstantExpression(cNode.getName())))));
  }

  private Statement createConstructorStatementMapSpecial(FieldNode fNode) {
    final Expression fieldExpr = new VariableExpression(fNode);
    Expression initExpr = fNode.getInitialValueExpression();
    if (initExpr == null) initExpr = new ConstantExpression(null);
    Expression namedArgs = findArg(fNode.getName());
    Expression baseArgs = new VariableExpression("args");
    return new IfStatement(
        equalsNullExpr(baseArgs),
        new IfStatement(
            equalsNullExpr(initExpr),
            new EmptyStatement(),
            assignStatement(fieldExpr, cloneCollectionExpr(initExpr))),
        new IfStatement(
            equalsNullExpr(namedArgs),
            new IfStatement(
                isTrueExpr(
                    new MethodCallExpression(
                        baseArgs, "containsKey", new ConstantExpression(fNode.getName()))),
                assignStatement(fieldExpr, namedArgs),
                assignStatement(fieldExpr, cloneCollectionExpr(baseArgs))),
            new IfStatement(
                isOneExpr(
                    new MethodCallExpression(baseArgs, "size", MethodCallExpression.NO_ARGUMENTS)),
                assignStatement(fieldExpr, cloneCollectionExpr(namedArgs)),
                assignStatement(fieldExpr, cloneCollectionExpr(baseArgs)))));
  }

  private void ensureNotPublic(String cNode, FieldNode fNode) {
    String fName = fNode.getName();
    // TODO: do we need to lock down things like: $ownClass
    if (fNode.isPublic() && !fName.contains("$") && !(fNode.isStatic() && fNode.isFinal())) {
      addError(
          "Public field '"
              + fName
              + "' not allowed for "
              + MY_TYPE_NAME
              + " class '"
              + cNode
              + "'.",
          fNode);
    }
  }

  private void addProperty(ClassNode cNode, PropertyNode pNode) {
    final FieldNode fn = pNode.getField();
    cNode.getFields().remove(fn);
    cNode.addProperty(
        pNode.getName(),
        pNode.getModifiers() | ACC_FINAL,
        pNode.getType(),
        pNode.getInitialExpression(),
        pNode.getGetterBlock(),
        pNode.getSetterBlock());
    final FieldNode newfn = cNode.getField(fn.getName());
    cNode.getFields().remove(newfn);
    cNode.addField(fn);
  }

  private boolean validateConstructors(ClassNode cNode) {
    if (cNode.getDeclaredConstructors().size() != 0) {
      // TODO: allow constructors which only call provided constructor?
      addError(
          "Explicit constructors not allowed for "
              + ImmutableASTTransformation.MY_TYPE_NAME
              + " class: "
              + cNode.getNameWithoutPackage(),
          cNode.getDeclaredConstructors().get(0));
    }
    return true;
  }

  private Statement createConstructorStatement(
      ClassNode cNode,
      PropertyNode pNode,
      List<String> knownImmutableClasses,
      List<String> knownImmutables) {
    FieldNode fNode = pNode.getField();
    final ClassNode fieldType = fNode.getType();
    Statement statement = null;
    if (fieldType.isArray() || isOrImplements(fieldType, CLONEABLE_TYPE)) {
      statement = createConstructorStatementArrayOrCloneable(fNode);
    } else if (isKnownImmutableClass(fieldType, knownImmutableClasses)
        || isKnownImmutable(pNode.getName(), knownImmutables)) {
      statement = createConstructorStatementDefault(fNode);
    } else if (fieldType.isDerivedFrom(DATE_TYPE)) {
      statement = createConstructorStatementDate(fNode);
    } else if (isOrImplements(fieldType, COLLECTION_TYPE)
        || fieldType.isDerivedFrom(COLLECTION_TYPE)
        || isOrImplements(fieldType, MAP_TYPE)
        || fieldType.isDerivedFrom(MAP_TYPE)) {
      statement = createConstructorStatementCollection(fNode);
    } else if (fieldType.isResolved()) {
      addError(
          createErrorMessage(cNode.getName(), fNode.getName(), fieldType.getName(), "compiling"),
          fNode);
      statement = EmptyStatement.INSTANCE;
    } else {
      statement = createConstructorStatementGuarded(cNode, fNode);
    }
    return statement;
  }

  private Statement createConstructorStatementGuarded(ClassNode cNode, FieldNode fNode) {
    final Expression fieldExpr = new VariableExpression(fNode);
    Expression initExpr = fNode.getInitialValueExpression();
    if (initExpr == null) initExpr = new ConstantExpression(null);
    Expression unknown = findArg(fNode.getName());
    return new IfStatement(
        equalsNullExpr(unknown),
        new IfStatement(
            equalsNullExpr(initExpr),
            new EmptyStatement(),
            assignStatement(fieldExpr, checkUnresolved(cNode, fNode, initExpr))),
        assignStatement(fieldExpr, checkUnresolved(cNode, fNode, unknown)));
  }

  private Expression checkUnresolved(ClassNode cNode, FieldNode fNode, Expression value) {
    Expression args =
        new TupleExpression(
            new MethodCallExpression(
                new VariableExpression("this"), "getClass", ArgumentListExpression.EMPTY_ARGUMENTS),
            new ConstantExpression(fNode.getName()),
            value);
    return new StaticMethodCallExpression(SELF_TYPE, "checkImmutable", args);
  }

  private Statement createConstructorStatementCollection(FieldNode fNode) {
    final Expression fieldExpr = new VariableExpression(fNode);
    Expression initExpr = fNode.getInitialValueExpression();
    if (initExpr == null) initExpr = new ConstantExpression(null);
    Expression collection = findArg(fNode.getName());
    ClassNode fieldType = fieldExpr.getType();
    return new IfStatement(
        equalsNullExpr(collection),
        new IfStatement(
            equalsNullExpr(initExpr),
            new EmptyStatement(),
            assignStatement(fieldExpr, cloneCollectionExpr(initExpr))),
        new IfStatement(
            isInstanceOf(collection, CLONEABLE_TYPE),
            assignStatement(
                fieldExpr, cloneCollectionExpr(cloneArrayOrCloneableExpr(collection, fieldType))),
            assignStatement(fieldExpr, cloneCollectionExpr(collection))));
  }

  private boolean isKnownImmutableClass(ClassNode fieldType, List<String> knownImmutableClasses) {
    if (!fieldType.isResolved()) return false;
    return fieldType.isEnum()
        || ClassHelper.isPrimitiveType(fieldType)
        || fieldType.getAnnotations(MY_TYPE).size() != 0
        || inImmutableList(fieldType.getName())
        || knownImmutableClasses.contains(fieldType.getName());
  }

  private boolean isKnownImmutable(String fieldName, List<String> knownImmutables) {
    return knownImmutables.contains(fieldName);
  }

  private static boolean inImmutableList(String typeName) {
    return immutableList.contains(typeName);
  }

  private Statement createConstructorStatementArrayOrCloneable(FieldNode fNode) {
    final Expression fieldExpr = new VariableExpression(fNode);
    Expression initExpr = fNode.getInitialValueExpression();
    ClassNode fieldType = fNode.getType();
    if (initExpr == null) initExpr = new ConstantExpression(null);
    final Expression array = findArg(fNode.getName());
    return new IfStatement(
        equalsNullExpr(array),
        new IfStatement(
            equalsNullExpr(initExpr),
            assignStatement(fieldExpr, new ConstantExpression(null)),
            assignStatement(fieldExpr, cloneArrayOrCloneableExpr(initExpr, fieldType))),
        assignStatement(fieldExpr, cloneArrayOrCloneableExpr(array, fieldType)));
  }

  private Statement createConstructorStatementDate(FieldNode fNode) {
    final Expression fieldExpr = new VariableExpression(fNode);
    Expression initExpr = fNode.getInitialValueExpression();
    if (initExpr == null) initExpr = new ConstantExpression(null);
    final Expression date = findArg(fNode.getName());
    return new IfStatement(
        equalsNullExpr(date),
        new IfStatement(
            equalsNullExpr(initExpr),
            assignStatement(fieldExpr, new ConstantExpression(null)),
            assignStatement(fieldExpr, cloneDateExpr(initExpr))),
        assignStatement(fieldExpr, cloneDateExpr(date)));
  }

  private Expression cloneDateExpr(Expression origDate) {
    return new ConstructorCallExpression(
        DATE_TYPE,
        new MethodCallExpression(origDate, "getTime", MethodCallExpression.NO_ARGUMENTS));
  }

  private void adjustPropertyForImmutability(PropertyNode pNode, List<PropertyNode> newNodes) {
    final FieldNode fNode = pNode.getField();
    fNode.setModifiers((pNode.getModifiers() & (~ACC_PUBLIC)) | ACC_FINAL | ACC_PRIVATE);
    adjustPropertyNode(pNode, createGetterBody(fNode));
    newNodes.add(pNode);
  }

  private void adjustPropertyNode(PropertyNode pNode, Statement getterBody) {
    pNode.setSetterBlock(null);
    pNode.setGetterBlock(getterBody);
  }

  private Statement createGetterBody(FieldNode fNode) {
    BlockStatement body = new BlockStatement();
    final ClassNode fieldType = fNode.getType();
    final Statement statement;
    if (fieldType.isArray() || isOrImplements(fieldType, CLONEABLE_TYPE)) {
      statement = createGetterBodyArrayOrCloneable(fNode);
    } else if (fieldType.isDerivedFrom(DATE_TYPE)) {
      statement = createGetterBodyDate(fNode);
    } else {
      statement = createGetterBodyDefault(fNode);
    }
    body.addStatement(statement);
    return body;
  }

  private static String createErrorMessage(
      String className, String fieldName, String typeName, String mode) {
    return MY_TYPE_NAME
        + " processor doesn't know how to handle field '"
        + fieldName
        + "' of type '"
        + prettyTypeName(typeName)
        + "' while "
        + mode
        + " class "
        + className
        + ".\n"
        + MY_TYPE_NAME
        + " classes only support properties with effectively immutable types including:\n"
        + "- Strings, primitive types, wrapper types, BigInteger and BigDecimal, enums\n"
        + "- other "
        + MY_TYPE_NAME
        + " classes and known immutables (java.awt.Color, java.net.URI)\n"
        + "- Cloneable classes, collections, maps and arrays, and other classes with special handling (java.util.Date)\n"
        + "Other restrictions apply, please see the groovydoc for "
        + MY_TYPE_NAME
        + " for further details";
  }

  private static String prettyTypeName(String name) {
    return name.equals("java.lang.Object") ? name + " or def" : name;
  }

  private Statement createGetterBodyArrayOrCloneable(FieldNode fNode) {
    final Expression fieldExpr = new VariableExpression(fNode);
    final Expression expression = cloneArrayOrCloneableExpr(fieldExpr, fNode.getType());
    return safeExpression(fieldExpr, expression);
  }

  private Statement createGetterBodyDate(FieldNode fNode) {
    final Expression fieldExpr = new VariableExpression(fNode);
    final Expression expression = cloneDateExpr(fieldExpr);
    return safeExpression(fieldExpr, expression);
  }

  /** This method exists to be binary compatible with 1.7 - 1.8.6 compiled code. */
  @SuppressWarnings("Unchecked")
  public static Object checkImmutable(String className, String fieldName, Object field) {
    if (field == null || field instanceof Enum || inImmutableList(field.getClass().getName()))
      return field;
    if (field instanceof Collection) return DefaultGroovyMethods.asImmutable((Collection) field);
    if (field.getClass().getAnnotation(MY_CLASS) != null) return field;
    final String typeName = field.getClass().getName();
    throw new RuntimeException(createErrorMessage(className, fieldName, typeName, "constructing"));
  }

  @SuppressWarnings("Unchecked")
  public static Object checkImmutable(Class<?> clazz, String fieldName, Object field) {
    Immutable immutable = (Immutable) clazz.getAnnotation(MY_CLASS);
    List<Class> knownImmutableClasses = new ArrayList<Class>();
    if (immutable != null && immutable.knownImmutableClasses().length > 0) {
      knownImmutableClasses = Arrays.asList(immutable.knownImmutableClasses());
    }

    if (field == null
        || field instanceof Enum
        || inImmutableList(field.getClass().getName())
        || knownImmutableClasses.contains(field.getClass())) return field;
    if (field instanceof Collection) return DefaultGroovyMethods.asImmutable((Collection) field);
    if (field.getClass().getAnnotation(MY_CLASS) != null) return field;
    final String typeName = field.getClass().getName();
    throw new RuntimeException(
        createErrorMessage(clazz.getName(), fieldName, typeName, "constructing"));
  }

  public static void checkPropNames(Object instance, Map<String, Object> args) {
    final MetaClass metaClass = InvokerHelper.getMetaClass(instance);
    for (String k : args.keySet()) {
      if (metaClass.hasProperty(instance, k) == null)
        throw new MissingPropertyException(k, instance.getClass());
    }
  }
}
  private void createFactoryMethods() {
    if (isAbstract(annotatedClass)) return;

    MethodBuilder.createPublicMethod("create")
        .returning(newClass(annotatedClass))
        .mod(Opcodes.ACC_STATIC)
        .namedParams("values")
        .optionalStringParam("name", keyField)
        .delegatingClosureParam(annotatedClass)
        .declareVariable(
            "result",
            keyField != null ? ctorX(annotatedClass, args("name")) : ctorX(annotatedClass))
        .callMethod("result", "copyFromTemplate")
        .callMethod("result", "apply", args("values", "closure"))
        .callValidationOn("result")
        .doReturn("result")
        .addTo(annotatedClass);

    MethodBuilder.createPublicMethod("create")
        .returning(newClass(annotatedClass))
        .mod(Opcodes.ACC_STATIC)
        .optionalStringParam("name", keyField)
        .delegatingClosureParam(annotatedClass)
        .doReturn(
            callX(
                annotatedClass,
                "create",
                keyField != null
                    ? args(new MapExpression(), varX("name"), varX("closure"))
                    : args(new MapExpression(), varX("closure"))))
        .addTo(annotatedClass);

    MethodBuilder.createPublicMethod("createFromScript")
        .returning(newClass(annotatedClass))
        .deprecated()
        .mod(Opcodes.ACC_STATIC)
        .classParam("configType", ClassHelper.SCRIPT_TYPE)
        .doReturn(callX(callX(varX("configType"), "newInstance"), "run"))
        .addTo(annotatedClass);

    if (keyField != null) {
      MethodBuilder.createPublicMethod("createFrom")
          .returning(newClass(annotatedClass))
          .mod(Opcodes.ACC_STATIC)
          .stringParam("name")
          .stringParam("text")
          .declareVariable(
              "simpleName",
              callX(
                  callX(
                      callX(callX(varX("name"), "tokenize", args(constX("."))), "first"),
                      "tokenize",
                      args(constX("/"))),
                  "last"))
          .declareVariable("result", callX(annotatedClass, "create", args("simpleName")))
          .declareVariable(
              "loader",
              ctorX(
                  ClassHelper.make(GroovyClassLoader.class),
                  args(
                      callX(
                          callX(ClassHelper.make(Thread.class), "currentThread"),
                          "getContextClassLoader"))))
          .declareVariable("config", ctorX(ClassHelper.make(CompilerConfiguration.class)))
          .assignS(
              propX(varX("config"), "scriptBaseClass"), constX(DelegatingScript.class.getName()))
          .declareVariable("binding", ctorX(ClassHelper.make(Binding.class)))
          .declareVariable(
              "shell",
              ctorX(ClassHelper.make(GroovyShell.class), args("loader", "binding", "config")))
          .declareVariable("script", callX(varX("shell"), "parse", args("text")))
          .callMethod("script", "setDelegate", args("result"))
          .callMethod("script", "run")
          .doReturn("result")
          .addTo(annotatedClass);

      MethodBuilder.createPublicMethod("createFromSnippet")
          .deprecated()
          .returning(newClass(annotatedClass))
          .mod(Opcodes.ACC_STATIC)
          .stringParam("name")
          .stringParam("text")
          .doReturn(callX(annotatedClass, "createFrom", args("name", "text")))
          .addTo(annotatedClass);
    } else {
      MethodBuilder.createPublicMethod("createFrom")
          .returning(newClass(annotatedClass))
          .mod(Opcodes.ACC_STATIC)
          .stringParam("text")
          .declareVariable("result", callX(annotatedClass, "create"))
          .declareVariable(
              "loader",
              ctorX(
                  ClassHelper.make(GroovyClassLoader.class),
                  args(
                      callX(
                          callX(ClassHelper.make(Thread.class), "currentThread"),
                          "getContextClassLoader"))))
          .declareVariable("config", ctorX(ClassHelper.make(CompilerConfiguration.class)))
          .assignS(
              propX(varX("config"), "scriptBaseClass"), constX(DelegatingScript.class.getName()))
          .declareVariable("binding", ctorX(ClassHelper.make(Binding.class)))
          .declareVariable(
              "shell",
              ctorX(ClassHelper.make(GroovyShell.class), args("loader", "binding", "config")))
          .declareVariable("script", callX(varX("shell"), "parse", args("text")))
          .callMethod("script", "setDelegate", args("result"))
          .callMethod("script", "run")
          .doReturn("result")
          .addTo(annotatedClass);

      MethodBuilder.createPublicMethod("createFromSnippet")
          .deprecated()
          .returning(newClass(annotatedClass))
          .mod(Opcodes.ACC_STATIC)
          .stringParam("text")
          .doReturn(callX(annotatedClass, "createFrom", args("text")))
          .addTo(annotatedClass);
    }

    MethodBuilder.createPublicMethod("createFrom")
        .returning(newClass(annotatedClass))
        .mod(Opcodes.ACC_STATIC)
        .param(make(File.class), "src")
        .doReturn(
            callX(
                annotatedClass,
                "createFromSnippet",
                args(callX(callX(varX("src"), "toURI"), "toURL"))))
        .addTo(annotatedClass);

    MethodBuilder.createPublicMethod("createFromSnippet")
        .deprecated()
        .returning(newClass(annotatedClass))
        .mod(Opcodes.ACC_STATIC)
        .param(make(File.class), "src")
        .doReturn(callX(annotatedClass, "createFrom", args("src")))
        .addTo(annotatedClass);

    MethodBuilder.createPublicMethod("createFrom")
        .returning(newClass(annotatedClass))
        .mod(Opcodes.ACC_STATIC)
        .param(make(URL.class), "src")
        .declareVariable("text", propX(varX("src"), "text"))
        .doReturn(
            callX(
                annotatedClass,
                "createFromSnippet",
                keyField != null ? args(propX(varX("src"), "path"), varX("text")) : args("text")))
        .addTo(annotatedClass);

    MethodBuilder.createPublicMethod("createFromSnippet")
        .deprecated()
        .returning(newClass(annotatedClass))
        .mod(Opcodes.ACC_STATIC)
        .param(make(URL.class), "src")
        .doReturn(callX(annotatedClass, "createFrom", args("src")))
        .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);
  }
}
 public ClassNode classNodeFor(String type) {
   return ClassHelper.make(type);
 }
/**
 * This visitor is responsible for amending the AST with static compilation metadata or transform
 * the AST so that a class or a method can be statically compiled. It may also throw errors specific
 * to static compilation which are not considered as an error at the type check pass. For example,
 * usage of spread operator is not allowed in statically compiled portions of code, while it may be
 * statically checked.
 *
 * <p>Static compilation relies on static type checking, which explains why this visitor extends the
 * type checker visitor.
 *
 * @author Cedric Champeau
 */
public class StaticCompilationVisitor extends StaticTypeCheckingVisitor {
  private static final ClassNode TYPECHECKED_CLASSNODE = ClassHelper.make(TypeChecked.class);
  private static final ClassNode COMPILESTATIC_CLASSNODE = ClassHelper.make(CompileStatic.class);
  private static final ClassNode[] TYPECHECKED_ANNOTATIONS = {
    TYPECHECKED_CLASSNODE, COMPILESTATIC_CLASSNODE
  };

  public static final ClassNode ARRAYLIST_CLASSNODE = ClassHelper.make(ArrayList.class);
  public static final MethodNode ARRAYLIST_CONSTRUCTOR;
  public static final MethodNode ARRAYLIST_ADD_METHOD =
      ARRAYLIST_CLASSNODE.getMethod(
          "add", new Parameter[] {new Parameter(ClassHelper.OBJECT_TYPE, "o")});

  static {
    ARRAYLIST_CONSTRUCTOR =
        new ConstructorNode(
            ACC_PUBLIC, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, EmptyStatement.INSTANCE);
    ARRAYLIST_CONSTRUCTOR.setDeclaringClass(StaticCompilationVisitor.ARRAYLIST_CLASSNODE);
  }

  private final TypeChooser typeChooser = new StaticTypesTypeChooser();

  private ClassNode classNode;

  public StaticCompilationVisitor(final SourceUnit unit, final ClassNode node) {
    super(unit, node);
  }

  @Override
  protected ClassNode[] getTypeCheckingAnnotations() {
    return TYPECHECKED_ANNOTATIONS;
  }

  public static boolean isStaticallyCompiled(AnnotatedNode node) {
    if (node.getNodeMetaData(STATIC_COMPILE_NODE) != null)
      return (Boolean) node.getNodeMetaData(STATIC_COMPILE_NODE);
    if (node instanceof MethodNode) {
      return isStaticallyCompiled(node.getDeclaringClass());
    }
    if (node instanceof InnerClassNode) {
      return isStaticallyCompiled(((InnerClassNode) node).getOuterClass());
    }
    return false;
  }

  private void addPrivateFieldAndMethodAccessors(ClassNode node) {
    addPrivateBridgeMethods(node);
    addPrivateFieldsAccessors(node);
    Iterator<InnerClassNode> it = node.getInnerClasses();
    while (it.hasNext()) {
      addPrivateFieldAndMethodAccessors(it.next());
    }
  }

  @Override
  public void visitClass(final ClassNode node) {
    boolean skip = shouldSkipClassNode(node);
    if (!skip && !anyMethodSkip(node)) {
      node.putNodeMetaData(MopWriter.Factory.class, StaticCompilationMopWriter.FACTORY);
    }
    ClassNode oldCN = classNode;
    classNode = node;
    Iterator<InnerClassNode> innerClasses = classNode.getInnerClasses();
    while (innerClasses.hasNext()) {
      InnerClassNode innerClassNode = innerClasses.next();
      boolean innerStaticCompile = !(skip || isSkippedInnerClass(innerClassNode));
      innerClassNode.putNodeMetaData(STATIC_COMPILE_NODE, innerStaticCompile);
      innerClassNode.putNodeMetaData(
          WriterControllerFactory.class, node.getNodeMetaData(WriterControllerFactory.class));
      if (innerStaticCompile && !anyMethodSkip(innerClassNode)) {
        innerClassNode.putNodeMetaData(MopWriter.Factory.class, StaticCompilationMopWriter.FACTORY);
      }
    }
    super.visitClass(node);
    addPrivateFieldAndMethodAccessors(node);
    classNode = oldCN;
  }

  private boolean anyMethodSkip(final ClassNode node) {
    for (MethodNode methodNode : node.getMethods()) {
      if (isSkipMode(methodNode)) return true;
    }
    return false;
  }

  /**
   * If we are in a constructor, that is static compiled, but in a class, that is not, it may happen
   * that init code from object initializers, fields or properties is added into the constructor
   * code. The backend assumes a purely static contructor, so it may fail if it encounters dynamic
   * code here. Thus we make this kind of code fail
   */
  private void checkForConstructorWithCSButClassWithout(MethodNode node) {
    if (!(node instanceof ConstructorNode)) return;
    Object meta = node.getNodeMetaData(STATIC_COMPILE_NODE);
    if (!Boolean.TRUE.equals(meta)) return;
    ClassNode clz = typeCheckingContext.getEnclosingClassNode();
    meta = clz.getNodeMetaData(STATIC_COMPILE_NODE);
    if (Boolean.TRUE.equals(meta)) return;
    if (clz.getObjectInitializerStatements().isEmpty()
        && clz.getFields().isEmpty()
        && clz.getProperties().isEmpty()) {
      return;
    }

    addStaticTypeError(
        "Cannot statically compile constructor implicitly including non static elements from object initializers, properties or fields.",
        node);
  }

  @Override
  public void visitMethod(final MethodNode node) {
    if (isSkipMode(node)) {
      node.putNodeMetaData(STATIC_COMPILE_NODE, false);
    }
    super.visitMethod(node);
    checkForConstructorWithCSButClassWithout(node);
  }

  /** Adds special accessors for private constants so that inner classes can retrieve them. */
  @SuppressWarnings("unchecked")
  private void addPrivateFieldsAccessors(ClassNode node) {
    Set<ASTNode> accessedFields =
        (Set<ASTNode>) node.getNodeMetaData(StaticTypesMarker.PV_FIELDS_ACCESS);
    if (accessedFields == null) return;
    Map<String, MethodNode> privateConstantAccessors =
        (Map<String, MethodNode>) node.getNodeMetaData(PRIVATE_FIELDS_ACCESSORS);
    if (privateConstantAccessors != null) {
      // already added
      return;
    }
    int acc = -1;
    privateConstantAccessors = new HashMap<String, MethodNode>();
    final int access = Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC;
    for (FieldNode fieldNode : node.getFields()) {
      if (accessedFields.contains(fieldNode)) {

        acc++;
        Parameter param = new Parameter(node.getPlainNodeReference(), "$that");
        Expression receiver =
            fieldNode.isStatic() ? new ClassExpression(node) : new VariableExpression(param);
        Statement stmt =
            new ExpressionStatement(new PropertyExpression(receiver, fieldNode.getName()));
        MethodNode accessor =
            node.addMethod(
                "pfaccess$" + acc,
                access,
                fieldNode.getOriginType(),
                new Parameter[] {param},
                ClassNode.EMPTY_ARRAY,
                stmt);
        privateConstantAccessors.put(fieldNode.getName(), accessor);
      }
    }
    node.setNodeMetaData(PRIVATE_FIELDS_ACCESSORS, privateConstantAccessors);
  }

  /**
   * This method is used to add "bridge" methods for private methods of an inner/outer class, so
   * that the outer class is capable of calling them. It does basically the same job as access$000
   * like methods in Java.
   *
   * @param node an inner/outer class node for which to generate bridge methods
   */
  @SuppressWarnings("unchecked")
  private void addPrivateBridgeMethods(final ClassNode node) {
    Set<ASTNode> accessedMethods =
        (Set<ASTNode>) node.getNodeMetaData(StaticTypesMarker.PV_METHODS_ACCESS);
    if (accessedMethods == null) return;
    List<MethodNode> methods = new ArrayList<MethodNode>(node.getAllDeclaredMethods());
    Map<MethodNode, MethodNode> privateBridgeMethods =
        (Map<MethodNode, MethodNode>) node.getNodeMetaData(PRIVATE_BRIDGE_METHODS);
    if (privateBridgeMethods != null) {
      // private bridge methods already added
      return;
    }
    privateBridgeMethods = new HashMap<MethodNode, MethodNode>();
    int i = -1;
    final int access = Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC;
    for (MethodNode method : methods) {
      if (accessedMethods.contains(method)) {
        i++;
        Parameter[] methodParameters = method.getParameters();
        Parameter[] newParams = new Parameter[methodParameters.length + 1];
        System.arraycopy(methodParameters, 0, newParams, 1, methodParameters.length);
        newParams[0] = new Parameter(node.getPlainNodeReference(), "$that");
        Expression arguments;
        if (method.getParameters() == null || method.getParameters().length == 0) {
          arguments = ArgumentListExpression.EMPTY_ARGUMENTS;
        } else {
          List<Expression> args = new LinkedList<Expression>();
          for (Parameter parameter : methodParameters) {
            args.add(new VariableExpression(parameter));
          }
          arguments = new ArgumentListExpression(args);
        }
        Expression receiver =
            method.isStatic() ? new ClassExpression(node) : new VariableExpression(newParams[0]);
        MethodCallExpression mce = new MethodCallExpression(receiver, method.getName(), arguments);
        mce.setMethodTarget(method);

        ExpressionStatement returnStatement = new ExpressionStatement(mce);
        MethodNode bridge =
            node.addMethod(
                "access$" + i,
                access,
                method.getReturnType(),
                newParams,
                method.getExceptions(),
                returnStatement);
        privateBridgeMethods.put(method, bridge);
        bridge.addAnnotation(new AnnotationNode(COMPILESTATIC_CLASSNODE));
      }
    }
    if (!privateBridgeMethods.isEmpty()) {
      node.setNodeMetaData(PRIVATE_BRIDGE_METHODS, privateBridgeMethods);
    }
  }

  private void memorizeInitialExpressions(final MethodNode node) {
    // add node metadata for default parameters because they are erased by the Verifier
    if (node.getParameters() != null) {
      for (Parameter parameter : node.getParameters()) {
        parameter.putNodeMetaData(
            StaticTypesMarker.INITIAL_EXPRESSION, parameter.getInitialExpression());
      }
    }
  }

  @Override
  public void visitSpreadExpression(final SpreadExpression expression) {}

  @Override
  public void visitMethodCallExpression(final MethodCallExpression call) {
    super.visitMethodCallExpression(call);

    MethodNode target = (MethodNode) call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
    if (target != null) {
      call.setMethodTarget(target);
      memorizeInitialExpressions(target);
    }

    if (call.getMethodTarget() == null && call.getLineNumber() > 0) {
      addError("Target method for method call expression hasn't been set", call);
    }
  }

  @Override
  public void visitConstructorCallExpression(final ConstructorCallExpression call) {
    super.visitConstructorCallExpression(call);

    MethodNode target = (MethodNode) call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
    if (target == null && call.getLineNumber() > 0) {
      addError("Target constructor for constructor call expression hasn't been set", call);
    } else {
      if (target == null) {
        // try to find a target
        ArgumentListExpression argumentListExpression =
            InvocationWriter.makeArgumentList(call.getArguments());
        List<Expression> expressions = argumentListExpression.getExpressions();
        ClassNode[] args = new ClassNode[expressions.size()];
        for (int i = 0; i < args.length; i++) {
          args[i] = typeChooser.resolveType(expressions.get(i), classNode);
        }
        MethodNode constructor =
            findMethodOrFail(
                call, call.isSuperCall() ? classNode.getSuperClass() : classNode, "<init>", args);
        call.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, constructor);
        target = constructor;
      }
    }
    if (target != null) {
      memorizeInitialExpressions(target);
    }
  }

  @Override
  public void visitForLoop(final ForStatement forLoop) {
    super.visitForLoop(forLoop);
    Expression collectionExpression = forLoop.getCollectionExpression();
    if (!(collectionExpression instanceof ClosureListExpression)) {
      final ClassNode collectionType = getType(forLoop.getCollectionExpression());
      ClassNode componentType = inferLoopElementType(collectionType);
      forLoop.getVariable().setType(componentType);
      forLoop.getVariable().setOriginType(componentType);
    }
  }

  @Override
  protected MethodNode findMethodOrFail(
      final Expression expr, final ClassNode receiver, final String name, final ClassNode... args) {
    MethodNode methodNode = super.findMethodOrFail(expr, receiver, name, args);
    if (expr instanceof BinaryExpression && methodNode != null) {
      expr.putNodeMetaData(BINARY_EXP_TARGET, new Object[] {methodNode, name});
    }
    return methodNode;
  }

  @Override
  protected boolean existsProperty(
      final PropertyExpression pexp,
      final boolean checkForReadOnly,
      final ClassCodeVisitorSupport visitor) {
    Expression objectExpression = pexp.getObjectExpression();
    ClassNode objectExpressionType = getType(objectExpression);
    final Reference<ClassNode> rType = new Reference<ClassNode>(objectExpressionType);
    ClassCodeVisitorSupport receiverMemoizer =
        new ClassCodeVisitorSupport() {
          @Override
          protected SourceUnit getSourceUnit() {
            return null;
          }

          public void visitField(final FieldNode node) {
            if (visitor != null) visitor.visitField(node);
            ClassNode declaringClass = node.getDeclaringClass();
            if (declaringClass != null) rType.set(declaringClass);
          }

          public void visitMethod(final MethodNode node) {
            if (visitor != null) visitor.visitMethod(node);
            ClassNode declaringClass = node.getDeclaringClass();
            if (declaringClass != null) rType.set(declaringClass);
          }

          @Override
          public void visitProperty(final PropertyNode node) {
            if (visitor != null) visitor.visitProperty(node);
            ClassNode declaringClass = node.getDeclaringClass();
            if (declaringClass != null) rType.set(declaringClass);
          }
        };
    boolean exists = super.existsProperty(pexp, checkForReadOnly, receiverMemoizer);
    if (exists) {
      if (objectExpression.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER) == null) {
        objectExpression.putNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER, rType.get());
      }
      if (StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(
          objectExpressionType, ClassHelper.LIST_TYPE)) {
        objectExpression.putNodeMetaData(
            COMPONENT_TYPE, inferComponentType(objectExpressionType, ClassHelper.int_TYPE));
      }
    }
    return exists;
  }

  @Override
  public void visitPropertyExpression(final PropertyExpression pexp) {
    super.visitPropertyExpression(pexp);
    Object dynamic = pexp.getNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION);
    if (dynamic != null) {
      pexp.getObjectExpression()
          .putNodeMetaData(StaticCompilationMetadataKeys.RECEIVER_OF_DYNAMIC_PROPERTY, dynamic);
    }
  }
}