Skip to content

Commit

Permalink
Change codegen to use @CustomType.Builder
Browse files Browse the repository at this point in the history
Part of #390
  • Loading branch information
pawelprazak committed Jul 14, 2022
1 parent 368d32c commit 877d9ec
Show file tree
Hide file tree
Showing 5 changed files with 583 additions and 792 deletions.
249 changes: 12 additions & 237 deletions pkg/codegen/java/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -713,14 +713,6 @@ func (pt *plainType) genBuilderHelpers(ctx *classFileContext, setterName,
}

func (pt *plainType) genOutputType(ctx *classFileContext) error {
if len(pt.properties) > 250 {
return pt.genJumboOutputType(ctx)
}
return pt.genNormalOutputType(ctx)
}

func (pt *plainType) genJumboOutputType(ctx *classFileContext) error {
// generates a class for Outputs where pt.properties >= 250
w := ctx.writer
const indent = " "

Expand Down Expand Up @@ -752,235 +744,8 @@ func (pt *plainType) genJumboOutputType(ctx *classFileContext) error {
fprintf(w, "\n")
}

// Generate the constructor parameter names - used as a workaround for Java reflection issues
var paramNamesStringBuilder strings.Builder
paramNamesStringBuilder.WriteString("{")
for i, prop := range props {
if i > 0 {
paramNamesStringBuilder.WriteString(",")
}
paramName := names.Ident(prop.Name)
paramNamesStringBuilder.WriteString("\"" + paramName.String() + "\"")
}
paramNamesStringBuilder.WriteString("}")

// Generate an appropriately-attributed constructor that will set this types' fields.
fprintf(w, " @%s.Constructor\n", ctx.ref(names.CustomType))
// Generate empty constructor, not that the instance created
// with this constructor may not be valid if there are 'required' fields.
if len(props) > 0 {
fprintf(w, "\n")
fprintf(w, " private %s() {\n", pt.name)
for _, prop := range props {
fieldName := names.Ident(pt.mod.propertyName(prop))
emptyValue := emptyTypeInitializer(ctx, prop.Type, true)
fprintf(w, " this.%s = %s;\n", fieldName, emptyValue)
}
fprintf(w, " }\n")
}

// Generate getters
for _, prop := range props {
paramName := names.Ident(prop.Name)
getterName := names.Ident(prop.Name).AsProperty().Getter()
getterType := pt.mod.typeString(
ctx,
prop.Type,
pt.propertyTypeQualifier,
false,
false,
true, // outer optional
false, // inputless overload
)
getterTypeNonOptional := pt.mod.typeString(
ctx,
codegen.UnwrapType(prop.Type),
pt.propertyTypeQualifier,
false,
false,
false, // outer optional (irrelevant)
false, // inputless overload
)

returnStatement := fmt.Sprintf("this.%s", paramName)

switch propType := prop.Type.(type) {
case *schema.OptionalType:
switch propType.ElementType.(type) {
case *schema.ArrayType:
getterType = getterTypeNonOptional
returnStatement = fmt.Sprintf("this.%s == null ? List.of() : this.%s", paramName, paramName)
case *schema.MapType:
getterType = getterTypeNonOptional
returnStatement = fmt.Sprintf("this.%s == null ? Map.of() : this.%s", paramName, paramName)
default:
// Option<Output<T>> are stored as @Nullable Output<T>. We don't
// need to perform the nullable conversion for them.
if !getterType.Type.Equal(names.Output) {
returnStatement = fmt.Sprintf("%s.ofNullable(this.%s)", ctx.ref(names.Optional), paramName)
}
}
}

genPropJavadoc(ctx, prop, propJavadocOptions{
indent: indent,
isGetter: true,
})
if err := getterTemplate.Execute(w, getterTemplateContext{
Indent: indent,
GetterType: getterType.ToCode(ctx.imports),
GetterName: getterName,
ReturnStatement: returnStatement,
}); err != nil {
return err
}

fprintf(w, "\n")
}

// Generate Builder
var builderFields []builderFieldTemplateContext
var builderSetters []builderSetterTemplateContext
for _, prop := range props {
propertyName := names.Ident(pt.mod.propertyName(prop))
propertyType := pt.mod.typeString(
ctx,
prop.Type,
pt.propertyTypeQualifier,
false, // is input
false, // requires initializers
false, // outer optional
false, // inputless overload
)

// add field
builderFields = append(builderFields, builderFieldTemplateContext{
FieldType: propertyType.ToCode(ctx.imports),
FieldName: propertyName.AsProperty().Field(),
})

setterName := names.Ident(prop.Name).AsProperty().Setter()
assignment := func(propertyName names.Ident) string {
if prop.IsRequired() {
return fmt.Sprintf("this.%s = %s.requireNonNull(%s)", propertyName, ctx.ref(names.Objects), propertyName)
}
return fmt.Sprintf("this.%s = %s", propertyName, propertyName)
}

// add setter
builderSetters = append(builderSetters, builderSetterTemplateContext{
SetterName: setterName,
PropertyType: propertyType.ToCode(ctx.imports),
PropertyName: propertyName.String(),
Assignment: assignment(propertyName),
ListType: propertyType.ListType(ctx),
})
}

fprintf(w, "\n")
if err := builderTemplate.Execute(w, builderTemplateContext{
Indent: indent,
Name: "Builder",
IsFinal: true,
IsJumbo: true,
Fields: builderFields,
Setters: builderSetters,
ResultType: pt.name,
Objects: ctx.ref(names.Objects),
}); err != nil {
return err
}
fprintf(w, "\n")

// Close the class.
fprintf(w, "}\n")
return nil
}

func (pt *plainType) genNormalOutputType(ctx *classFileContext) error {
w := ctx.writer
const indent = " "

props := pt.properties

// Open the class and annotate it appropriately.
fprintf(w, "@%s\n", ctx.ref(names.CustomType))
fprintf(w, "public final class %s {\n", pt.name)

// Generate each output field.
for _, prop := range props {
fieldName := names.Ident(pt.mod.propertyName(prop))
fieldType := pt.mod.typeString(
ctx,
prop.Type,
pt.propertyTypeQualifier,
false,
false,
false, // outer optional
false, // inputless overload
)
genPropJavadoc(ctx, prop, propJavadocOptions{
indent: indent,
isGetter: true,
})
fprintf(w, " private final %s %s;\n", fieldType.ToCode(ctx.imports), fieldName)
}
if len(props) > 0 {
fprintf(w, "\n")
}

// Generate an appropriately-attributed constructor that will set this types' fields.
fprintf(w, " @%s.Constructor\n", ctx.ref(names.CustomType))
fprintf(w, " private %s(", pt.name)

// Generate the constructor parameters.
for i, prop := range props {
// TODO: factor this out (with similar code in genInputType)
paramName := names.Ident(prop.Name)
paramType := pt.mod.typeString(
ctx,
prop.Type,
pt.propertyTypeQualifier,
false,
false,
false, // outer optional
false, // inputless overload
)

if i == 0 && len(props) > 1 { // first param
fprintf(w, "\n")
}

terminator := ""
if i != len(props)-1 { // not last param
terminator = ",\n"
}

paramDef := fmt.Sprintf("%s %s %s%s",
fmt.Sprintf("@%s.Parameter(\"%s\")", ctx.ref(names.CustomType), prop.Name),
paramType.ToCode(ctx.imports), paramName, terminator)
if len(props) > 1 {
paramDef = fmt.Sprintf(" %s", paramDef)
}
fprintf(w, "%s", paramDef)
}

fprintf(w, ") {\n")

// Generate the constructor body.
for _, prop := range props {
paramName := names.Ident(prop.Name)
fieldName := names.Ident(pt.mod.propertyName(prop))

// Never `Objects.requireNotNull` here because we need
// to tolerate providers failing to return required props.
//
// See https://github.com/pulumi/pulumi-java/issues/164
fprintf(w, " this.%s = %s;\n", fieldName, paramName)

}
fprintf(w, " }\n")
fprintf(w, "\n")
// Generate a private constructor.
fprintf(w, " private %s() {}\n", pt.name)

// Generate getters
for _, prop := range props {
Expand Down Expand Up @@ -1071,12 +836,19 @@ func (pt *plainType) genNormalOutputType(ctx *classFileContext) error {
}

// add setter
var setterAnnotation string
if setterName != prop.Name {
setterAnnotation = fmt.Sprintf("@%s.Setter(\"%s\")", ctx.ref(names.CustomType), prop.Name)
} else {
setterAnnotation = fmt.Sprintf("@%s.Setter", ctx.ref(names.CustomType))
}
builderSetters = append(builderSetters, builderSetterTemplateContext{
SetterName: setterName,
PropertyType: propertyType.ToCode(ctx.imports),
PropertyName: propertyName.String(),
Assignment: assignment(propertyName),
ListType: propertyType.ListType(ctx),
Annotations: []string{setterAnnotation},
})
}

Expand All @@ -1089,6 +861,9 @@ func (pt *plainType) genNormalOutputType(ctx *classFileContext) error {
Setters: builderSetters,
ResultType: pt.name,
Objects: ctx.ref(names.Objects),
Annotations: []string{
fmt.Sprintf("@%s.Builder", ctx.ref(names.CustomType)),
},
}); err != nil {
return err
}
Expand Down
49 changes: 20 additions & 29 deletions pkg/codegen/java/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ type builderSetterTemplateContext struct {
PropertyName string
Assignment string
ListType string
Annotations []string
}

type builderFieldTemplateContext struct {
Expand All @@ -154,62 +155,52 @@ const builderTemplateText = `{{ .Indent }}public static {{ .Name }} builder() {
{{ .Indent }} return new {{ .Name }}(defaults);
{{ .Indent }}}
{{- range $annotation := .Annotations }}
{{ $.Indent }}{{ $annotation }}
{{- end }}
{{ .Indent }}public static {{ if .IsFinal }}final {{ end }}class {{ .Name }} {
{{- range $field := .Fields }}
{{ $.Indent }} private {{ $field.FieldType }} {{ $field.FieldName }}{{ $field.Initialization }};
{{- end }}
{{ $.Indent }} public {{ .Name }}() {
{{ $.Indent }} // Empty
{{ $.Indent }} }
{{ $.Indent }} public {{ .Name }}() {}
{{ $.Indent }} public {{ .Name }}({{ .ResultType }} defaults) {
{{ $.Indent }} {{ .Objects }}.requireNonNull(defaults);
{{- range $field := .Fields }}
{{ $.Indent }} this.{{ $field.FieldName }} = defaults.{{ $field.FieldName }};
{{- end }}
{{ $.Indent }} }
{{ range $setter := .Setters }}
{{- range $annotation := $setter.Annotations }}
{{ $.Indent }} {{ $annotation }}
{{- end }}
{{ $.Indent }} public {{ $.Name }} {{ $setter.SetterName }}({{ $setter.PropertyType }} {{ $setter.PropertyName }}) {
{{ $.Indent }} {{ $setter.Assignment }};
{{ $.Indent }} return this;
{{ $.Indent }} }
{{- if $setter.ListType }}
{{ $.Indent }} public {{ $.Name }} {{ $setter.SetterName }}({{ $setter.ListType }}... {{ $setter.PropertyName }}) {
{{ $.Indent }} return {{ $setter.SetterName }}(List.of({{ $setter.PropertyName }}));
{{ $.Indent }} }
{{- end -}}
{{ end }}
{{- if .IsJumbo -}}
{{ $.Indent }} public {{ .ResultType }} build() {
{{ $.Indent }} final var built = new {{ .ResultType }}();
{{ range $i, $field := .Fields }}
{{ $.Indent }} built.{{ $field.FieldName }} = {{ $field.FieldName }};
{{- end }}
{{ $.Indent }} return built;
{{- else -}}
{{ $.Indent }} public {{ .ResultType }} build() {
{{ $.Indent }} return new {{ .ResultType }}(
{{- $last := (len .Fields | sub 1) -}}
{{- range $i, $field := .Fields -}}
{{ $field.FieldName }}{{ if not (eq $i $last) }}, {{ end }}
{{- end -}}
);
{{ $.Indent }} final var o = new {{ .ResultType }}();
{{- range $i, $field := .Fields }}
{{ $.Indent }} o.{{ $field.FieldName }} = {{ $field.FieldName }};
{{- end }}
{{ $.Indent }} return o;
{{ .Indent }} }
{{ .Indent }}}`

var builderTemplate = Template("Builder", builderTemplateText)

type builderTemplateContext struct {
Indent string
Name string
IsFinal bool
IsJumbo bool
Fields []builderFieldTemplateContext
Setters []builderSetterTemplateContext
ResultType string
Objects string
Indent string
Name string
IsFinal bool
Fields []builderFieldTemplateContext
Setters []builderSetterTemplateContext
ResultType string
Objects string
Annotations []string
}
Loading

0 comments on commit 877d9ec

Please sign in to comment.