private void writeSetter(ClassVisitor visitor, Type generatedType, ModelProperty<?> property) {
    WeaklyTypeReferencingMethod<?, Void> weakSetter = property.getSetter();
    // There is no setter for this property
    if (weakSetter == null) {
      return;
    }

    String propertyName = property.getName();
    Class<?> propertyClass = property.getType().getConcreteClass();
    Type propertyType = Type.getType(propertyClass);
    Label calledOutsideOfConstructor = new Label();

    Method setter = weakSetter.getMethod();

    // the regular typed setter
    String methodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, propertyType);
    MethodVisitor methodVisitor =
        declareMethod(
            visitor, setter.getName(), methodDescriptor, AsmClassGeneratorUtils.signature(setter));

    putCanCallSettersFieldValueOnStack(methodVisitor, generatedType);
    jumpToLabelIfStackEvaluatesToTrue(methodVisitor, calledOutsideOfConstructor);
    throwExceptionBecauseCalledOnItself(methodVisitor);

    methodVisitor.visitLabel(calledOutsideOfConstructor);
    putStateFieldValueOnStack(methodVisitor, generatedType);
    putConstantOnStack(methodVisitor, propertyName);
    putFirstMethodArgumentOnStack(methodVisitor, propertyType);
    if (propertyClass.isPrimitive()) {
      boxType(methodVisitor, propertyClass);
    }
    invokeStateSetMethod(methodVisitor);

    finishVisitingMethod(methodVisitor);
  }
  private void writePropertyMethods(
      ClassVisitor visitor,
      Type generatedType,
      StructSchema<?> viewSchema,
      StructSchema<?> delegateSchema) {
    Collection<String> delegatePropertyNames;
    if (delegateSchema != null) {
      delegatePropertyNames = delegateSchema.getPropertyNames();
    } else {
      delegatePropertyNames = Collections.emptySet();
    }
    Class<?> viewClass = viewSchema.getType().getConcreteClass();
    for (ModelProperty<?> property : viewSchema.getProperties()) {
      String propertyName = property.getName();

      writeConfigureMethod(visitor, generatedType, property);
      writeSetMethod(visitor, generatedType, property);
      createTypeConvertingSetter(visitor, generatedType, property);

      // Delegated properties are handled in writeDelegateMethods()
      if (delegatePropertyNames.contains(propertyName)) {
        continue;
      }
      switch (property.getStateManagementType()) {
        case MANAGED:
          writeGetters(visitor, generatedType, property);
          writeSetter(visitor, generatedType, property);
          break;

        case UNMANAGED:
          for (WeaklyTypeReferencingMethod<?, ?> getter : property.getGetters()) {
            Method getterMethod = getter.getMethod();
            if (!Modifier.isFinal(getterMethod.getModifiers())
                && !propertyName.equals("metaClass")) {
              writeNonAbstractMethodWrapper(visitor, generatedType, viewClass, getterMethod);
            }
          }
          break;
      }
    }
  }
  private void writeSetMethod(ClassVisitor visitor, Type generatedType, ModelProperty<?> property) {
    if (property.isWritable() && property.getSchema() instanceof ScalarValueSchema) {

      // TODO - should we support this?
      // Adds a void $propName(Object value) method that sets the value
      MethodVisitor methodVisitor =
          declareMethod(
              visitor,
              property.getName(),
              Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE),
              null);
      putThisOnStack(methodVisitor);
      putFirstMethodArgumentOnStack(methodVisitor);
      methodVisitor.visitMethodInsn(
          INVOKEVIRTUAL,
          generatedType.getInternalName(),
          property.getSetter().getName(),
          Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE),
          false);
      finishVisitingMethod(methodVisitor);
    }
  }
  private void writeGetters(ClassVisitor visitor, Type generatedType, ModelProperty<?> property) {
    Class<?> propertyClass = property.getType().getConcreteClass();
    Type propertyType = Type.getType(propertyClass);
    Set<String> processedNames = Sets.newHashSet();
    for (WeaklyTypeReferencingMethod<?, ?> weakGetter : property.getGetters()) {
      Method getter = weakGetter.getMethod();
      if (!processedNames.add(getter.getName())) {
        continue;
      }
      MethodVisitor methodVisitor =
          declareMethod(
              visitor,
              getter.getName(),
              Type.getMethodDescriptor(propertyType),
              AsmClassGeneratorUtils.signature(getter));

      putStateFieldValueOnStack(methodVisitor, generatedType);
      putConstantOnStack(methodVisitor, property.getName());
      invokeStateGetMethod(methodVisitor);
      castFirstStackElement(methodVisitor, propertyClass);
      finishVisitingMethod(methodVisitor, returnCode(propertyType));
    }
  }
  private void writeConfigureMethod(
      ClassVisitor visitor, Type generatedType, ModelProperty<?> property) {
    if (!property.isWritable() && property.getSchema() instanceof CompositeSchema) {
      // Adds a void $propName(Closure<?> cl) method that delegates to model state

      MethodVisitor methodVisitor =
          declareMethod(
              visitor,
              property.getName(),
              Type.getMethodDescriptor(Type.VOID_TYPE, CLOSURE_TYPE),
              null);
      putStateFieldValueOnStack(methodVisitor, generatedType);
      putConstantOnStack(methodVisitor, property.getName());
      putFirstMethodArgumentOnStack(methodVisitor);
      methodVisitor.visitMethodInsn(
          INVOKEINTERFACE,
          MODEL_ELEMENT_STATE_TYPE_INTERNAL_NAME,
          "apply",
          STATE_APPLY_METHOD_DESCRIPTOR,
          true);
      finishVisitingMethod(methodVisitor);
      return;
    }
    if (!property.isWritable() && property.getSchema() instanceof UnmanagedImplStructSchema) {
      UnmanagedImplStructSchema<?> structSchema =
          (UnmanagedImplStructSchema<?>) property.getSchema();
      if (!structSchema.isAnnotated()) {
        return;
      }

      // Adds a void $propName(Closure<?> cl) method that executes the closure
      MethodVisitor methodVisitor =
          declareMethod(
              visitor,
              property.getName(),
              Type.getMethodDescriptor(Type.VOID_TYPE, CLOSURE_TYPE),
              null);
      putThisOnStack(methodVisitor);
      methodVisitor.visitMethodInsn(
          INVOKEVIRTUAL,
          generatedType.getInternalName(),
          property.getGetters().get(0).getName(),
          Type.getMethodDescriptor(Type.getType(property.getType().getConcreteClass())),
          false);
      putFirstMethodArgumentOnStack(methodVisitor);
      methodVisitor.visitMethodInsn(
          INVOKESTATIC,
          Type.getInternalName(ClosureBackedAction.class),
          "execute",
          Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE, CLOSURE_TYPE),
          false);
      finishVisitingMethod(methodVisitor);
      return;
    }

    // Adds a void $propName(Closure<?> cl) method that throws MME, to avoid attempts to convert
    // closure to something else
    MethodVisitor methodVisitor =
        declareMethod(
            visitor,
            property.getName(),
            Type.getMethodDescriptor(Type.VOID_TYPE, CLOSURE_TYPE),
            null);
    putThisOnStack(methodVisitor);
    putConstantOnStack(methodVisitor, property.getName());
    methodVisitor.visitInsn(Opcodes.ICONST_1);
    methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, OBJECT_TYPE.getInternalName());
    methodVisitor.visitInsn(Opcodes.DUP);
    methodVisitor.visitInsn(Opcodes.ICONST_0);
    putFirstMethodArgumentOnStack(methodVisitor);
    methodVisitor.visitInsn(Opcodes.AASTORE);
    methodVisitor.visitMethodInsn(
        INVOKEVIRTUAL,
        generatedType.getInternalName(),
        "methodMissing",
        METHOD_MISSING_METHOD_DESCRIPTOR,
        false);
    finishVisitingMethod(methodVisitor);
  }