private void generateBuilder(JType optionsDeclaredInNode, BuilderModel model) {
    boolean markGenerated = model.isMarkGenerated();

    JSourceFile source = optionsDeclaredInNode.getCompilationUnit().getSource();
    JType pojo = source.getMainType();
    JType builder;

    boolean isAbstract = model.isSupportSubclassing();

    if (pojo.getSimpleName().equals(model.getBuilderTypeSimpleRaw())) {
      builder = pojo;
    } else {
      builder = pojo.getChildTypeWithNameOrNull(model.getBuilderTypeSimpleRaw());
      if (builder == null) {
        SourceTemplate t =
            newSourceTemplate()
                .var("typeBounds", model.getPojoType().getTypeBoundsOrNull())
                .var(
                    "type",
                    model.getBuilderTypeSimpleRaw()
                        + Strings.nullToEmpty(model.getBuilderTypeBoundsOrNull()))
                .var("modifier", (isAbstract ? "abstract" : ""))
                .pl("public static ${modifier} class ${type} { }");

        pojo.asMutator(getContext()).addType(t.asResolvedTypeNodeNamed(null));
        builder = pojo.getChildTypeWithName(model.getBuilderTypeSimpleRaw());
        getGeneratorMeta().addGeneratedMarkers(builder.asAbstractTypeDecl());
      }
    }
    // generate the with() method and aliases
    generateStaticBuilderCreateMethods(model, pojo);
    // TODO:builder ctor
    // TODO:builder clone/from method

    // add the self() method
    if (!"this".equals(model.getBuilderSelfAccessor())) {
      SourceTemplate selfMethod =
          newSourceTemplate()
              .var("selfType", model.getBuilderSelfType())
              .var("selfGetter", model.getBuilderSelfAccessor())
              .pl("protected ${selfType} ${selfGetter} { return (${selfType})this; }");

      addMethod(builder, selfMethod.asMethodNodeSnippet(), markGenerated);
    }

    for (BuilderPropertyModel property : model.getProperties()) {
      if (property.isWriteable()) {
        generateField(markGenerated, builder, property);
        generateSetter(model, markGenerated, builder, property);
        generateCollectionAddRemove(builder, model, property);
        generateMapAddRemove(builder, model, property);
      }
    }

    generateAllArgCtor(pojo, model);
    generateBuildMethod(builder, model);

    writeToDiskIfChanged(source);
  }
  private void generateAllArgCtor(JType beanType, BuilderModel model) {
    if (!beanType.isAbstract()) {
      SourceTemplate beanCtor =
          newSourceTemplate()
              .var("b.name", model.getPojoType().getSimpleNameRaw())
              .pl("private ${b.name} (");

      boolean comma = false;
      // args
      for (BuilderPropertyModel property : model.getProperties()) {
        if (property.isReadOnly()) {
          continue;
        }
        if (comma) {
          beanCtor.p(",");
        }
        if (model.isMarkCtorArgsAsProperties()) {
          beanCtor.p(
              "@" + Property.class.getName() + "(name=\"" + property.getPropertyName() + "\") ");
        }
        beanCtor.p(property.getType().getFullName() + " " + property.getPropertyName());
        comma = true;
      }

      beanCtor.pl("){");
      // field assignments
      for (BuilderPropertyModel property : model.getProperties()) {
        if (property.isReadOnly()) {
          continue;
        }
        // TODO:figure out if we can set field directly, via setter, or via ctor args
        // for now assume a setter
        if (property.isFromSuperClass()) {
          beanCtor.pl(property.getPropertySetterName() + "(" + property.getPropertyName() + ");");
        } else {
          beanCtor.pl("this." + property.getFieldName() + "=" + property.getPropertyName() + ";");
        }
        comma = true;
      }
      beanCtor.pl("}");
      addMethod(beanType, beanCtor.asConstructorNodeSnippet(), model.isMarkGenerated());
    }
  }