private emitClassBuilder()

in packages/jsii-pacmak/lib/targets/java.ts [2071:2288]


  private emitClassBuilder(cls: spec.ClassType) {
    // Not rendering if there is no initializer, or if the initializer is protected or variadic
    if (cls.initializer == null || cls.initializer.protected) {
      return;
    }
    // Not rendering if the initializer has no parameters
    if (cls.initializer.parameters == null) {
      return;
    }
    // Not rendering if there is a nested "Builder" class
    if (
      this.reflectAssembly.tryFindType(`${cls.fqn}.${BUILDER_CLASS_NAME}`) !=
      null
    ) {
      return;
    }

    // Find the first struct parameter of the constructor (if any)
    const firstStruct = cls.initializer.parameters.find((param) => {
      if (!spec.isNamedTypeReference(param.type)) {
        return false;
      }
      const paramType = this.reflectAssembly.tryFindType(param.type.fqn);
      return paramType?.isDataType();
    });

    // Not rendering if there is no struct parameter
    if (firstStruct == null) {
      return;
    }

    const structType = this.reflectAssembly.findType(
      (firstStruct.type as spec.NamedTypeReference).fqn,
    ) as reflect.InterfaceType;
    const structParamName = this.code.toCamelCase(
      JavaGenerator.safeJavaPropertyName(firstStruct.name),
    );
    const structBuilder = `${this.toJavaType(
      firstStruct.type,
    )}.${BUILDER_CLASS_NAME}`;

    const positionalParams = cls.initializer.parameters
      .filter((p) => p !== firstStruct)
      .map((param) => ({
        param,
        fieldName: this.code.toCamelCase(
          JavaGenerator.safeJavaPropertyName(param.name),
        ),
        javaType: this.toJavaType(param.type),
      }));

    const builtType = this.toJavaType(cls);

    this.code.line();
    this.code.line('/**');
    // eslint-disable-next-line prettier/prettier
    this.code.line(
      ` * ${stabilityPrefixFor(
        cls.initializer,
      )}A fluent builder for {@link ${builtType}}.`,
    );
    this.code.line(' */');
    this.emitStabilityAnnotations(cls.initializer);
    this.code.openBlock(
      `public static final class ${BUILDER_CLASS_NAME} implements software.amazon.jsii.Builder<${builtType}>`,
    );

    // Static factory method(s)
    for (const params of computeOverrides(positionalParams)) {
      const dummyMethod: spec.Method = {
        docs: {
          stability: cls.initializer.docs?.stability ?? cls.docs?.stability,
          returns: `a new instance of {@link ${BUILDER_CLASS_NAME}}.`,
        },
        name: 'create',
        parameters: params.map((param) => param.param),
      };
      this.addJavaDocs(dummyMethod, {
        api: 'member',
        fqn: cls.fqn,
        memberName: dummyMethod.name,
      });
      this.emitStabilityAnnotations(cls.initializer);
      this.code.openBlock(
        `public static ${BUILDER_CLASS_NAME} create(${params
          .map(
            (param) =>
              `final ${param.javaType}${param.param.variadic ? '...' : ''} ${
                param.fieldName
              }`,
          )
          .join(', ')})`,
      );
      this.code.line(
        `return new ${BUILDER_CLASS_NAME}(${positionalParams
          .map((param, idx) => (idx < params.length ? param.fieldName : 'null'))
          .join(', ')});`,
      );
      this.code.closeBlock();
    }

    // Private properties
    this.code.line();
    for (const param of positionalParams) {
      this.code.line(
        `private final ${param.javaType}${param.param.variadic ? '[]' : ''} ${
          param.fieldName
        };`,
      );
    }
    this.code.line(
      `private ${
        firstStruct.optional ? '' : 'final '
      }${structBuilder} ${structParamName};`,
    );

    // Private constructor
    this.code.line();
    this.code.openBlock(
      `private ${BUILDER_CLASS_NAME}(${positionalParams
        .map(
          (param) =>
            `final ${param.javaType}${param.param.variadic ? '...' : ''} ${
              param.fieldName
            }`,
        )
        .join(', ')})`,
    );
    for (const param of positionalParams) {
      this.code.line(`this.${param.fieldName} = ${param.fieldName};`);
    }
    if (!firstStruct.optional) {
      this.code.line(`this.${structParamName} = new ${structBuilder}();`);
    }
    this.code.closeBlock();

    // Fields
    for (const prop of structType.allProperties) {
      const fieldName = this.code.toCamelCase(
        JavaGenerator.safeJavaPropertyName(prop.name),
      );
      this.code.line();
      const setter: spec.Method = {
        name: fieldName,
        docs: {
          ...prop.spec.docs,
          stability: prop.spec.docs?.stability,
          returns: '{@code this}',
        },
        parameters: [
          {
            name: fieldName,
            type: spec.CANONICAL_ANY, // We don't quite care in this context!
            docs: prop.spec.docs,
          },
        ],
      };
      for (const javaType of this.toJavaTypes(prop.type.spec!, {
        covariant: true,
      })) {
        this.addJavaDocs(setter, {
          api: 'member',
          fqn: prop.definingType.fqn, // Could be inherited
          memberName: prop.name,
        });
        this.emitStabilityAnnotations(prop.spec);
        this.code.openBlock(
          `public ${BUILDER_CLASS_NAME} ${fieldName}(final ${javaType} ${fieldName})`,
        );
        this.code.line(
          `this.${structParamName}${
            firstStruct.optional ? '()' : ''
          }.${fieldName}(${fieldName});`,
        );
        this.code.line('return this;');
        this.code.closeBlock();
      }
    }

    // Final build method
    this.code.line();
    this.code.line('/**');
    this.code.line(
      ` * @return a newly built instance of {@link ${builtType}}.`,
    );
    this.code.line(' */');
    this.emitStabilityAnnotations(cls.initializer);
    this.code.line('@Override');
    this.code.openBlock(`public ${builtType} build()`);
    const params = cls.initializer.parameters.map((param) => {
      if (param === firstStruct) {
        return firstStruct.optional
          ? `this.${structParamName} != null ? this.${structParamName}.build() : null`
          : `this.${structParamName}.build()`;
      }
      return `this.${
        positionalParams.find((p) => param === p.param)!.fieldName
      }`;
    });
    this.code.indent(`return new ${builtType}(`);
    params.forEach((param, idx) =>
      this.code.line(`${param}${idx < params.length - 1 ? ',' : ''}`),
    );
    this.code.unindent(');');
    this.code.closeBlock();

    // Optional builder initialization
    if (firstStruct.optional) {
      this.code.line();
      this.code.openBlock(`private ${structBuilder} ${structParamName}()`);
      this.code.openBlock(`if (this.${structParamName} == null)`);
      this.code.line(`this.${structParamName} = new ${structBuilder}();`);
      this.code.closeBlock();
      this.code.line(`return this.${structParamName};`);
      this.code.closeBlock();
    }
    this.code.closeBlock();
  }