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