Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom jumpo type sunset #744

Merged
merged 3 commits into from
Jul 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
### Improvements

- [codegen] @CustomType builders now use the new @Builder and @Setter annotations
this is a breaking change and the generated code depends on SDK 0.5.0

### Bug Fixes
274 changes: 12 additions & 262 deletions pkg/codegen/java/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,31 +365,6 @@ func (mod *modContext) typeStringForEnumType(enumType *schema.EnumType) TypeShap
return TypeShape{Type: fqn}
}

// Returns a constructor for an empty instance of type `t`.
//
// optionalAsNull is used in conjunction with the `typeString` parameter
// `outerOptional` to ensure types line up.
// In general, `outerOptional` <=> `!optionalAsNull`.
func emptyTypeInitializer(ctx *classFileContext, t schema.Type, optionalAsNull bool) string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a win to me! I had doubts this is fully corret in all cases.

if isInputType(t) {
return fmt.Sprintf("%s.empty()", ctx.ref(names.Codegen))
}
if _, ok := t.(*schema.OptionalType); ok && !optionalAsNull {
return fmt.Sprintf("%s.empty()", ctx.ref(names.Optional))
}
switch codegen.UnwrapType(t).(type) {
case *schema.ArrayType:
return fmt.Sprintf("%s.of()", ctx.ref(names.List))
case *schema.MapType:
return fmt.Sprintf("%s.of()", ctx.ref(names.Map))
// TODO: should we return an "empty Either" (not sure what that means exactly)
// case *schema.UnionType:
// return "null"
default:
return "null"
}
}

type plainType struct {
mod *modContext
res *schema.Resource
Expand Down Expand Up @@ -713,14 +688,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 +719,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 +811,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 +836,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
Loading