diff --git a/packages/appsync-modelgen-plugin/src/__tests__/visitors/__snapshots__/appsync-java-visitor.test.ts.snap b/packages/appsync-modelgen-plugin/src/__tests__/visitors/__snapshots__/appsync-java-visitor.test.ts.snap index f4a9a0cf8..f27aab9b0 100644 --- a/packages/appsync-modelgen-plugin/src/__tests__/visitors/__snapshots__/appsync-java-visitor.test.ts.snap +++ b/packages/appsync-modelgen-plugin/src/__tests__/visitors/__snapshots__/appsync-java-visitor.test.ts.snap @@ -7494,6 +7494,206 @@ public final class Todo implements Model { " `; +exports[`AppSyncModelVisitor should avoid name collision on builder step 1`] = ` +"package com.amplifyframework.datastore.generated.model; + +import com.amplifyframework.core.model.temporal.Temporal; + +import java.util.List; +import java.util.UUID; +import java.util.Objects; + +import androidx.core.util.ObjectsCompat; + +import com.amplifyframework.core.model.Model; +import com.amplifyframework.core.model.annotations.Index; +import com.amplifyframework.core.model.annotations.ModelConfig; +import com.amplifyframework.core.model.annotations.ModelField; +import com.amplifyframework.core.model.query.predicate.QueryField; + +import static com.amplifyframework.core.model.query.predicate.QueryField.field; + +/** This is an auto generated class representing the MyObject type in your schema. */ +@SuppressWarnings(\\"all\\") +@ModelConfig(pluralName = \\"MyObjects\\") +public final class MyObject implements Model { + public static final QueryField ID = field(\\"MyObject\\", \\"id\\"); + public static final QueryField TUTORIAL = field(\\"MyObject\\", \\"tutorial\\"); + public static final QueryField FORM_CUES = field(\\"MyObject\\", \\"formCues\\"); + private final @ModelField(targetType=\\"ID\\", isRequired = true) String id; + private final @ModelField(targetType=\\"TutorialStep\\", isRequired = true) List tutorial; + private final @ModelField(targetType=\\"FormCue\\", isRequired = true) List formCues; + private @ModelField(targetType=\\"AWSDateTime\\", isReadOnly = true) Temporal.DateTime createdAt; + private @ModelField(targetType=\\"AWSDateTime\\", isReadOnly = true) Temporal.DateTime updatedAt; + public String getId() { + return id; + } + + public List getTutorial() { + return tutorial; + } + + public List getFormCues() { + return formCues; + } + + public Temporal.DateTime getCreatedAt() { + return createdAt; + } + + public Temporal.DateTime getUpdatedAt() { + return updatedAt; + } + + private MyObject(String id, List tutorial, List formCues) { + this.id = id; + this.tutorial = tutorial; + this.formCues = formCues; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if(obj == null || getClass() != obj.getClass()) { + return false; + } else { + MyObject myObject = (MyObject) obj; + return ObjectsCompat.equals(getId(), myObject.getId()) && + ObjectsCompat.equals(getTutorial(), myObject.getTutorial()) && + ObjectsCompat.equals(getFormCues(), myObject.getFormCues()) && + ObjectsCompat.equals(getCreatedAt(), myObject.getCreatedAt()) && + ObjectsCompat.equals(getUpdatedAt(), myObject.getUpdatedAt()); + } + } + + @Override + public int hashCode() { + return new StringBuilder() + .append(getId()) + .append(getTutorial()) + .append(getFormCues()) + .append(getCreatedAt()) + .append(getUpdatedAt()) + .toString() + .hashCode(); + } + + @Override + public String toString() { + return new StringBuilder() + .append(\\"MyObject {\\") + .append(\\"id=\\" + String.valueOf(getId()) + \\", \\") + .append(\\"tutorial=\\" + String.valueOf(getTutorial()) + \\", \\") + .append(\\"formCues=\\" + String.valueOf(getFormCues()) + \\", \\") + .append(\\"createdAt=\\" + String.valueOf(getCreatedAt()) + \\", \\") + .append(\\"updatedAt=\\" + String.valueOf(getUpdatedAt())) + .append(\\"}\\") + .toString(); + } + + public static TutorialBuildStep builder() { + return new Builder(); + } + + /** + * WARNING: This method should not be used to build an instance of this object for a CREATE mutation. + * This is a convenience method to return an instance of the object with only its ID populated + * to be used in the context of a parameter in a delete mutation or referencing a foreign key + * in a relationship. + * @param id the id of the existing item this instance will represent + * @return an instance of this model with only ID populated + */ + public static MyObject justId(String id) { + return new MyObject( + id, + null, + null + ); + } + + public CopyOfBuilder copyOfBuilder() { + return new CopyOfBuilder(id, + tutorial, + formCues); + } + public interface TutorialBuildStep { + FormCuesStep tutorial(List tutorial); + } + + + public interface FormCuesStep { + BuildStep formCues(List formCues); + } + + + public interface BuildStep { + MyObject build(); + BuildStep id(String id); + } + + + public static class Builder implements TutorialBuildStep, FormCuesStep, BuildStep { + private String id; + private List tutorial; + private List formCues; + @Override + public MyObject build() { + String id = this.id != null ? this.id : UUID.randomUUID().toString(); + + return new MyObject( + id, + tutorial, + formCues); + } + + @Override + public FormCuesStep tutorial(List tutorial) { + Objects.requireNonNull(tutorial); + this.tutorial = tutorial; + return this; + } + + @Override + public BuildStep formCues(List formCues) { + Objects.requireNonNull(formCues); + this.formCues = formCues; + return this; + } + + /** + * @param id id + * @return Current Builder instance, for fluent method chaining + */ + public BuildStep id(String id) { + this.id = id; + return this; + } + } + + + public final class CopyOfBuilder extends Builder { + private CopyOfBuilder(String id, List tutorial, List formCues) { + super.id(id); + super.tutorial(tutorial) + .formCues(formCues); + } + + @Override + public CopyOfBuilder tutorial(List tutorial) { + return (CopyOfBuilder) super.tutorial(tutorial); + } + + @Override + public CopyOfBuilder formCues(List formCues) { + return (CopyOfBuilder) super.formCues(formCues); + } + } + +} +" +`; + exports[`AppSyncModelVisitor should generate Temporal type for AWSDate* scalars 1`] = ` "package com.amplifyframework.datastore.generated.model; diff --git a/packages/appsync-modelgen-plugin/src/__tests__/visitors/appsync-java-visitor.test.ts b/packages/appsync-modelgen-plugin/src/__tests__/visitors/appsync-java-visitor.test.ts index c9959a159..cf748940f 100644 --- a/packages/appsync-modelgen-plugin/src/__tests__/visitors/appsync-java-visitor.test.ts +++ b/packages/appsync-modelgen-plugin/src/__tests__/visitors/appsync-java-visitor.test.ts @@ -11,17 +11,12 @@ const defaultJavaVisitorSettings = { transformerVersion: 1, generate: CodeGenGenerateEnum.code, respectPrimaryKeyAttributesOnConnectionField: false, -} +}; const buildSchemaWithDirectives = (schema: String): GraphQLSchema => { return buildSchema([schema, directives, scalars].join('\n')); }; - -const getVisitor = ( - schema: string, - selectedType?: string, - settings: any = {} -) => { +const getVisitor = (schema: string, selectedType?: string, settings: any = {}) => { const visitorConfig = { ...defaultJavaVisitorSettings, ...settings }; const ast = parse(schema); const builtSchema = buildSchemaWithDirectives(schema); @@ -31,7 +26,7 @@ const getVisitor = ( directives, target: 'java', scalars: JAVA_SCALAR_MAP, - ...visitorConfig + ...visitorConfig, }, { selectedType }, ); @@ -39,11 +34,7 @@ const getVisitor = ( return visitor; }; -const getVisitorPipelinedTransformer = ( - schema: string, - selectedType?: string, - settings: any = {} -) => { +const getVisitorPipelinedTransformer = (schema: string, selectedType?: string, settings: any = {}) => { return getVisitor(schema, selectedType, { ...settings, transformerVersion: 2 }); }; @@ -215,6 +206,30 @@ describe('AppSyncModelVisitor', () => { expect(generatedCode).toMatchSnapshot(); }); + it('should avoid name collision on builder step', () => { + const schema = /* GraphQL */ ` + type TutorialStep { + position: Int! + text: String! + } + + type FormCue { + position: Int! + text: String! + } + + type MyObject @model { + id: ID! + tutorial: [TutorialStep!]! + formCues: [FormCue!]! + } + `; + const visitor = getVisitor(schema, 'MyObject'); + const generatedCode = visitor.generate(); + expect(() => validateJava(generatedCode)).not.toThrow(); + expect(generatedCode).toMatchSnapshot(); + }); + describe('vNext transformer feature parity tests', () => { it('should produce the same result for @primaryKey as the primary key variant of @key', async () => { const schemaV1 = /* GraphQL */ ` @@ -596,7 +611,7 @@ describe('AppSyncModelVisitor', () => { content: String tags: [Tag] @manyToMany(relationName: "PostTags") } - + type Tag @model { id: ID! label: String! @@ -614,17 +629,17 @@ describe('AppSyncModelVisitor', () => { type Blog @model { id: ID! name: String! - blogOwner: BlogOwnerWithCustomPKS!@belongsTo + blogOwner: BlogOwnerWithCustomPKS! @belongsTo posts: [Post] @hasMany } - + type BlogOwnerWithCustomPKS @model { id: ID! - name: String!@primaryKey(sortKeyFields: ["wea"]) + name: String! @primaryKey(sortKeyFields: ["wea"]) wea: String! blogs: [Blog] @hasMany } - + type Post @model { postId: ID! @primaryKey title: String! @@ -637,32 +652,40 @@ describe('AppSyncModelVisitor', () => { type Comment @model { post: Post @belongsTo - title: String! @primaryKey(sortKeyFields: ["content","likes"]) + title: String! @primaryKey(sortKeyFields: ["content", "likes"]) content: String! likes: Int! description: String } `; it('Should generate correct model file for default id as primary key type', () => { - const generatedCode = getVisitorPipelinedTransformer(schema, `Blog`, { respectPrimaryKeyAttributesOnConnectionField: true }).generate(); + const generatedCode = getVisitorPipelinedTransformer(schema, `Blog`, { + respectPrimaryKeyAttributesOnConnectionField: true, + }).generate(); expect(() => validateJava(generatedCode)).not.toThrow(); expect(generatedCode).toMatchSnapshot(); }); it('Should generate correct model file for custom primary key type', () => { - const generatedCode = getVisitorPipelinedTransformer(schema, `Post`, { respectPrimaryKeyAttributesOnConnectionField: true }).generate(); + const generatedCode = getVisitorPipelinedTransformer(schema, `Post`, { + respectPrimaryKeyAttributesOnConnectionField: true, + }).generate(); expect(() => validateJava(generatedCode)).not.toThrow(); expect(generatedCode).toMatchSnapshot(); }); it('Should generate correct model file for composite key type without id field defined', () => { - const generatedCode = getVisitorPipelinedTransformer(schema, `Comment`, { respectPrimaryKeyAttributesOnConnectionField: true }).generate(); + const generatedCode = getVisitorPipelinedTransformer(schema, `Comment`, { + respectPrimaryKeyAttributesOnConnectionField: true, + }).generate(); expect(generatedCode).toMatchSnapshot(); }); it('Should generate correct model file for composite key type with id field defined', () => { - const generatedCode = getVisitorPipelinedTransformer(schema, `BlogOwnerWithCustomPKS`, { respectPrimaryKeyAttributesOnConnectionField: true }).generate(); + const generatedCode = getVisitorPipelinedTransformer(schema, `BlogOwnerWithCustomPKS`, { + respectPrimaryKeyAttributesOnConnectionField: true, + }).generate(); expect(generatedCode).toMatchSnapshot(); }); }); - + describe('Custom primary key for connected model tests', () => { it('Should generate correct model file for hasOne & belongsTo relation with composite primary key when CPK is enabled', () => { const schema = /* GraphQL */ ` @@ -678,8 +701,12 @@ describe('AppSyncModelVisitor', () => { project: Project @belongsTo } `; - const generatedCodeProject = getVisitorPipelinedTransformer(schema, `Project`, { respectPrimaryKeyAttributesOnConnectionField: true }).generate(); - const generatedCodeTeam = getVisitorPipelinedTransformer(schema, `Team`, { respectPrimaryKeyAttributesOnConnectionField: true }).generate(); + const generatedCodeProject = getVisitorPipelinedTransformer(schema, `Project`, { + respectPrimaryKeyAttributesOnConnectionField: true, + }).generate(); + const generatedCodeTeam = getVisitorPipelinedTransformer(schema, `Team`, { + respectPrimaryKeyAttributesOnConnectionField: true, + }).generate(); expect(generatedCodeProject).toMatchSnapshot(); expect(generatedCodeTeam).toMatchSnapshot(); }); @@ -690,14 +717,18 @@ describe('AppSyncModelVisitor', () => { title: String! comments: [Comment] @hasMany } - + type Comment @model { id: ID! @primaryKey(sortKeyFields: ["content"]) content: String! } `; - const generatedCodePost = getVisitorPipelinedTransformer(schema, 'Post', { respectPrimaryKeyAttributesOnConnectionField: true }).generate(); - const generatedCodeComment = getVisitorPipelinedTransformer(schema, 'Comment', { respectPrimaryKeyAttributesOnConnectionField: true }).generate(); + const generatedCodePost = getVisitorPipelinedTransformer(schema, 'Post', { + respectPrimaryKeyAttributesOnConnectionField: true, + }).generate(); + const generatedCodeComment = getVisitorPipelinedTransformer(schema, 'Comment', { + respectPrimaryKeyAttributesOnConnectionField: true, + }).generate(); expect(generatedCodePost).toMatchSnapshot(); expect(generatedCodeComment).toMatchSnapshot(); }); @@ -713,7 +744,9 @@ describe('AppSyncModelVisitor', () => { rating: Float! } `; - const generatedCodeMyPost = getVisitorPipelinedTransformer(schema, `MyPost`, { respectPrimaryKeyAttributesOnConnectionField: true }).generate(); + const generatedCodeMyPost = getVisitorPipelinedTransformer(schema, `MyPost`, { + respectPrimaryKeyAttributesOnConnectionField: true, + }).generate(); expect(generatedCodeMyPost).toMatchSnapshot(); }); it('Should generate ModelIdentifier factory with resolveIdentifier returning Java types matching graphql scalar conversion', () => { @@ -725,15 +758,21 @@ describe('AppSyncModelVisitor', () => { type IdModel @model { customKey: ID! @primaryKey } - + type IntModel @model { customKey: Int! @primaryKey } `; - const generatedCodeStringModel= getVisitorPipelinedTransformer(schema, 'StringModel', { respectPrimaryKeyAttributesOnConnectionField: true }).generate(); - const generatedCodeIdModel = getVisitorPipelinedTransformer(schema, 'IdModel', { respectPrimaryKeyAttributesOnConnectionField: true }).generate(); - const generatedCodeIntModel = getVisitorPipelinedTransformer(schema, 'IntModel', { respectPrimaryKeyAttributesOnConnectionField: true }).generate(); - + const generatedCodeStringModel = getVisitorPipelinedTransformer(schema, 'StringModel', { + respectPrimaryKeyAttributesOnConnectionField: true, + }).generate(); + const generatedCodeIdModel = getVisitorPipelinedTransformer(schema, 'IdModel', { + respectPrimaryKeyAttributesOnConnectionField: true, + }).generate(); + const generatedCodeIntModel = getVisitorPipelinedTransformer(schema, 'IntModel', { + respectPrimaryKeyAttributesOnConnectionField: true, + }).generate(); + expect(generatedCodeStringModel).toMatchSnapshot(); expect(generatedCodeIdModel).toMatchSnapshot(); expect(generatedCodeIntModel).toMatchSnapshot(); diff --git a/packages/appsync-modelgen-plugin/src/visitors/appsync-java-visitor.ts b/packages/appsync-modelgen-plugin/src/visitors/appsync-java-visitor.ts index d9d5541ee..97f20ce69 100644 --- a/packages/appsync-modelgen-plugin/src/visitors/appsync-java-visitor.ts +++ b/packages/appsync-modelgen-plugin/src/visitors/appsync-java-visitor.ts @@ -13,7 +13,14 @@ import { } from '../configs/java-config'; import { JAVA_TYPE_IMPORT_MAP } from '../scalars'; import { JavaDeclarationBlock } from '../languages/java-declaration-block'; -import { AppSyncModelVisitor, CodeGenField, CodeGenModel, CodeGenPrimaryKeyType, ParsedAppSyncModelConfig, RawAppSyncModelConfig } from './appsync-visitor'; +import { + AppSyncModelVisitor, + CodeGenField, + CodeGenModel, + CodeGenPrimaryKeyType, + ParsedAppSyncModelConfig, + RawAppSyncModelConfig, +} from './appsync-visitor'; import { CodeGenConnectionType } from '../utils/process-connections'; import { AuthDirective, AuthStrategy } from '../utils/process-auth'; import { printWarning } from '../utils/warn'; @@ -31,10 +38,7 @@ export class AppSyncModelJavaVisitor< const shouldUseModelNameFieldInHasManyAndBelongsTo = true; // This flag is going to be used to tight-trigger on JS implementations only. const shouldImputeKeyForUniDirectionalHasMany = false; - this.processDirectives( - shouldUseModelNameFieldInHasManyAndBelongsTo, - shouldImputeKeyForUniDirectionalHasMany - ); + this.processDirectives(shouldUseModelNameFieldInHasManyAndBelongsTo, shouldImputeKeyForUniDirectionalHasMany); if (this._parsedConfig.generate === 'loader') { return this.generateClassLoader(); @@ -377,7 +381,13 @@ export class AppSyncModelJavaVisitor< * @param classDeclaration */ protected generateIdentifierClassField(model: CodeGenModel, classDeclaration: JavaDeclarationBlock): void { - classDeclaration.addClassMember(this.getModelIdentifierClassFieldName(model), this.getModelIdentifierClassName(model), '', undefined, 'private'); + classDeclaration.addClassMember( + this.getModelIdentifierClassFieldName(model), + this.getModelIdentifierClassName(model), + '', + undefined, + 'private', + ); } /** * Generate step builder interfaces for each non-null field in the model @@ -386,11 +396,15 @@ export class AppSyncModelJavaVisitor< protected generateStepBuilderInterfaces(model: CodeGenModel, isIdAsModelPrimaryKey: boolean = true): JavaDeclarationBlock[] { const nonNullableFields = this.getWritableFields(model).filter(field => this.isRequiredField(field)); const nullableFields = this.getWritableFields(model).filter(field => !this.isRequiredField(field)); - const requiredInterfaces = nonNullableFields.filter((field: CodeGenField) => !(isIdAsModelPrimaryKey && this.READ_ONLY_FIELDS.includes(field.name))); + const requiredInterfaces = nonNullableFields.filter( + (field: CodeGenField) => !(isIdAsModelPrimaryKey && this.READ_ONLY_FIELDS.includes(field.name)), + ); + const types = this.getTypesUsedByModel(model); + const interfaces = requiredInterfaces.map((field, idx) => { const isLastField = requiredInterfaces.length - 1 === idx ? true : false; const returnType = isLastField ? 'Build' : requiredInterfaces[idx + 1].name; - const interfaceName = this.getStepInterfaceName(field.name); + const interfaceName = this.getStepInterfaceName(field.name, types); const methodName = this.getStepFunctionName(field); const argumentType = this.getNativeType(field); const argumentName = this.getStepFunctionArgumentName(field); @@ -398,14 +412,16 @@ export class AppSyncModelJavaVisitor< .asKind('interface') .withName(interfaceName) .access('public'); - interfaceDeclaration.withBlock(indent(`${this.getStepInterfaceName(returnType)} ${methodName}(${argumentType} ${argumentName});`)); + interfaceDeclaration.withBlock( + indent(`${this.getStepInterfaceName(returnType, types)} ${methodName}(${argumentType} ${argumentName});`), + ); return interfaceDeclaration; }); // Builder const builder = new JavaDeclarationBlock() .asKind('interface') - .withName(this.getStepInterfaceName('Build')) + .withName(this.getStepInterfaceName('Build', types)) .access('public'); const builderBody = []; // build method @@ -413,13 +429,13 @@ export class AppSyncModelJavaVisitor< if (isIdAsModelPrimaryKey) { // id method. Special case as this can throw exception - builderBody.push(`${this.getStepInterfaceName('Build')} id(String id);`); + builderBody.push(`${this.getStepInterfaceName('Build', types)} id(String id);`); } nullableFields.forEach(field => { const fieldName = this.getStepFunctionArgumentName(field); const methodName = this.getStepFunctionName(field); - builderBody.push(`${this.getStepInterfaceName('Build')} ${methodName}(${this.getNativeType(field)} ${fieldName});`); + builderBody.push(`${this.getStepInterfaceName('Build', types)} ${methodName}(${this.getNativeType(field)} ${fieldName});`); }); builder.withBlock(indentMultiline(builderBody.join('\n'))); @@ -434,15 +450,18 @@ export class AppSyncModelJavaVisitor< protected generateBuilderClass(model: CodeGenModel, classDeclaration: JavaDeclarationBlock, isIdAsModelPrimaryKey: boolean = true): void { const nonNullableFields = this.getWritableFields(model).filter(field => this.isRequiredField(field)); const nullableFields = this.getWritableFields(model).filter(field => !this.isRequiredField(field)); - const stepFields = nonNullableFields.filter((field: CodeGenField) => !(isIdAsModelPrimaryKey && this.READ_ONLY_FIELDS.includes(field.name))); - const stepInterfaces = stepFields.map((field: CodeGenField) => this.getStepInterfaceName(field.name)); + const stepFields = nonNullableFields.filter( + (field: CodeGenField) => !(isIdAsModelPrimaryKey && this.READ_ONLY_FIELDS.includes(field.name)), + ); + const types = this.getTypesUsedByModel(model); + const stepInterfaces = stepFields.map((field: CodeGenField) => this.getStepInterfaceName(field.name, types)); const builderClassDeclaration = new JavaDeclarationBlock() .access('public') .static() .asKind('class') .withName('Builder') - .implements([...stepInterfaces, this.getStepInterfaceName('Build')]); + .implements([...stepInterfaces, this.getStepInterfaceName('Build', types)]); // Add private instance fields [...nonNullableFields, ...nullableFields].forEach((field: CodeGenField) => { @@ -452,7 +471,9 @@ export class AppSyncModelJavaVisitor< // methods // build(); - const buildImplementation = isIdAsModelPrimaryKey ? [`String id = this.id != null ? this.id : UUID.randomUUID().toString();`, ''] : ['']; + const buildImplementation = isIdAsModelPrimaryKey + ? [`String id = this.id != null ? this.id : UUID.randomUUID().toString();`, ''] + : ['']; const buildParams = this.getWritableFields(model) .map(field => this.getFieldName(field)) .join(',\n'); @@ -473,7 +494,7 @@ export class AppSyncModelJavaVisitor< const isLastStep = idx === fields.length - 1; const fieldName = this.getFieldName(field); const methodName = this.getStepFunctionName(field); - const returnType = isLastStep ? this.getStepInterfaceName('Build') : this.getStepInterfaceName(fields[idx + 1].name); + const returnType = isLastStep ? this.getStepInterfaceName('Build', types) : this.getStepInterfaceName(fields[idx + 1].name, types); const argumentType = this.getNativeType(field); const argumentName = this.getStepFunctionArgumentName(field); const body = [`Objects.requireNonNull(${argumentName});`, `this.${fieldName} = ${argumentName};`, `return this;`].join('\n'); @@ -493,7 +514,7 @@ export class AppSyncModelJavaVisitor< nullableFields.forEach((field: CodeGenField) => { const fieldName = this.getFieldName(field); const methodName = this.getStepFunctionName(field); - const returnType = this.getStepInterfaceName('Build'); + const returnType = this.getStepInterfaceName('Build', types); const argumentType = this.getNativeType(field); const argumentName = this.getStepFunctionArgumentName(field); const body = [`this.${fieldName} = ${argumentName};`, `return this;`].join('\n'); @@ -520,7 +541,7 @@ export class AppSyncModelJavaVisitor< builderClassDeclaration.addClassMethod( 'id', - this.getStepInterfaceName('Build'), + this.getStepInterfaceName('Build', types), indentMultiline(idBuildStepBody), [{ name: 'id', type: 'String' }], [], @@ -541,7 +562,11 @@ export class AppSyncModelJavaVisitor< * @param model * @param classDeclaration */ - protected generateCopyOfBuilderClass(model: CodeGenModel, classDeclaration: JavaDeclarationBlock, isIdAsModelPrimaryKey: boolean = true): void { + protected generateCopyOfBuilderClass( + model: CodeGenModel, + classDeclaration: JavaDeclarationBlock, + isIdAsModelPrimaryKey: boolean = true, + ): void { const builderName = 'CopyOfBuilder'; const copyOfBuilderClassDeclaration = new JavaDeclarationBlock() .access('public') @@ -616,11 +641,11 @@ export class AppSyncModelJavaVisitor< primaryKeyClassDeclaration.addClassMember('serialVersionUID', 'long', '1L', [], 'private', { static: true, final: true }); // constructor const primaryKeyComponentFields: CodeGenField[] = [primaryKeyField, ...sortKeyFields]; - const constructorParams = primaryKeyComponentFields.map(field => ({name: this.getFieldName(field), type: this.getNativeType(field)})); + const constructorParams = primaryKeyComponentFields.map(field => ({ name: this.getFieldName(field), type: this.getNativeType(field) })); const constructorImpl = `super(${primaryKeyComponentFields.map(field => this.getFieldName(field)).join(', ')});`; primaryKeyClassDeclaration.addClassMethod(modelPrimaryKeyClassName, null, constructorImpl, constructorParams, [], 'public'); classDeclaration.nestedClass(primaryKeyClassDeclaration); -} + } /** * adds a copyOfBuilder method to the Model class. This method is used to create a copy of the model to mutate it @@ -647,12 +672,27 @@ export class AppSyncModelJavaVisitor< const body = isCompositeKey ? [ `if (${modelIdentifierClassFieldName} == null) {`, - indent(`this.${modelIdentifierClassFieldName} = new ${this.getModelIdentifierClassName(model)}(${[primaryKeyField, ...sortKeyFields].map(f => this.getFieldName(f)).join(', ')});`), + indent( + `this.${modelIdentifierClassFieldName} = new ${this.getModelIdentifierClassName(model)}(${[primaryKeyField, ...sortKeyFields] + .map(f => this.getFieldName(f)) + .join(', ')});`, + ), '}', - `return ${modelIdentifierClassFieldName};` + `return ${modelIdentifierClassFieldName};`, ].join('\n') : `return ${this.getFieldName(primaryKeyField)};`; - declarationsBlock.addClassMethod('resolveIdentifier', returnType, body, [], [], 'public', {}, ["Deprecated"], [], "@deprecated This API is internal to Amplify and should not be used."); + declarationsBlock.addClassMethod( + 'resolveIdentifier', + returnType, + body, + [], + [], + 'public', + {}, + ['Deprecated'], + [], + '@deprecated This API is internal to Amplify and should not be used.', + ); } /** @@ -823,11 +863,18 @@ export class AppSyncModelJavaVisitor< * @param model * @param classDeclaration */ - protected generateBuilderMethod(model: CodeGenModel, classDeclaration: JavaDeclarationBlock, isIdAsModelPrimaryKey: boolean = true): void { + protected generateBuilderMethod( + model: CodeGenModel, + classDeclaration: JavaDeclarationBlock, + isIdAsModelPrimaryKey: boolean = true, + ): void { const requiredFields = this.getWritableFields(model).filter( field => !field.isNullable && !(isIdAsModelPrimaryKey && this.READ_ONLY_FIELDS.includes(field.name)), ); - const returnType = requiredFields.length ? this.getStepInterfaceName(requiredFields[0].name) : this.getStepInterfaceName('Build'); + const types = this.getTypesUsedByModel(model); + const returnType = requiredFields.length + ? this.getStepInterfaceName(requiredFields[0].name, types) + : this.getStepInterfaceName('Build', types); classDeclaration.addClassMethod( 'builder', returnType, @@ -843,10 +890,27 @@ export class AppSyncModelJavaVisitor< /** * Generate the name of the step builder interface * @param nextFieldName: string + * @param types: string - set of types for all fields on model * @returns string */ - private getStepInterfaceName(nextFieldName: string): string { - return `${pascalCase(nextFieldName)}Step`; + private getStepInterfaceName(nextFieldName: string, types: Set): string { + const pascalCaseFieldName = pascalCase(nextFieldName); + const stepInterfaceName = `${pascalCaseFieldName}Step`; + + if (types.has(stepInterfaceName)) { + return `${pascalCaseFieldName}BuildStep`; + } + + return stepInterfaceName; + } + + /** + * Get set of all types used by a model + * @param model + * @return Set + */ + private getTypesUsedByModel(model: CodeGenModel): Set { + return new Set(model.fields.map(field => field.type)); } protected generateModelAnnotations(model: CodeGenModel): string[] {