import { indent , indentMultiline , transformComment } from '@graphql-codegen/visitor-plugin-common' ; import { camelCase , constantCase , pascalCase } from 'change-case' ; import dedent from 'ts-dedent' ; import { MODEL_CLASS_IMPORT_PACKAGES , GENERATED_PACKAGE_NAME , LOADER_CLASS_NAME , LOADER_IMPORT_PACKAGES , CONNECTION_RELATIONSHIP_IMPORTS , NON_MODEL_CLASS_IMPORT_PACKAGES , MODEL_AUTH_CLASS_IMPORT_PACKAGES , } from '../configs/java-config' ; import { JAVA_TYPE_IMPORT_MAP } from '../scalars' ; import { JavaDeclarationBlock } from '../languages/java-declaration-block' ; import { AppSyncModelVisitor , CodeGenField , CodeGenModel , ParsedAppSyncModelConfig , RawAppSyncModelConfig } from './appsync-visitor' ; import { CodeGenConnectionType } from '../utils/process-connections' ; import { AuthDirective , AuthStrategy } from '../utils/process-auth' ; import { printWarning } from '../utils/warn' ; import { validateFieldName } from '../utils/validate-field-name' ; export class AppSyncModelJavaVisitor < TRawConfig extends RawAppSyncModelConfig = RawAppSyncModelConfig , TPluginConfig extends ParsedAppSyncModelConfig = ParsedAppSyncModelConfig > extends AppSyncModelVisitor < TRawConfig , TPluginConfig > { protected additionalPackages : Set < string > = new Set ( ) ; protected usingAuth : boolean = false ; generate ( ) { const shouldUseModelNameFieldInHasManyAndBelongsTo = true ; this . processDirectives ( shouldUseModelNameFieldInHasManyAndBelongsTo ) ; if ( this . _parsedConfig . generate === 'loader' ) { return this . generateClassLoader ( ) ; } validateFieldName ( { ... this . getSelectedModels ( ) , ... this . getSelectedNonModels ( ) } ) ; if ( this . selectedTypeIsEnum ( ) ) { return this . generateEnums ( ) ; } else if ( this . selectedTypeIsNonModel ( ) ) { return this . generateNonModelClasses ( ) ; } return this . generateModelClasses ( ) ; } generateClassLoader ( ) { const AMPLIFY_MODEL_VERSION = 'AMPLIFY_MODEL_VERSION' ; const result = [ this . generatePackageName ( ) , '' , this . generateImportStatements ( LOADER_IMPORT_PACKAGES ) ] ; result . push ( transformComment ( dedent ` Contains the set of model classes that implement {@link Model}
    interface.` ) ) ; const loaderClassDeclaration = new JavaDeclarationBlock ( ) . withName ( LOADER_CLASS_NAME ) . access ( 'public' ) . final ( ) . asKind ( 'class' ) . implements ( [ 'ModelProvider' ] ) ; loaderClassDeclaration . addClassMember ( AMPLIFY_MODEL_VERSION , 'String' , `"${ this . computeVersion ( ) }"` , [ ] , 'private' , { final : true , static : true , } ) ; loaderClassDeclaration . addClassMember ( 'amplifyGeneratedModelInstance' , LOADER_CLASS_NAME , '' , [ ] , 'private' , { static : true } ) ; loaderClassDeclaration . addClassMethod ( LOADER_CLASS_NAME , null , '' , [ ] , [ ] , 'private' ) ; const getInstanceBody = dedent `
    if (amplifyGeneratedModelInstance == null) {
      amplifyGeneratedModelInstance = new ${ LOADER_CLASS_NAME }();
    }
    return amplifyGeneratedModelInstance;` ; loaderClassDeclaration . addClassMethod ( 'getInstance' , LOADER_CLASS_NAME , getInstanceBody , [ ] , [ ] , 'public' , { static : true , synchronized : true , } ) ; const modelsMethodDocString = dedent `
    Get a set of the model classes.

    @return a set of the model classes.` ; const classList = Object . values ( this . modelMap ) . map ( model => `${ this . getModelName ( model ) }.class` ) . join ( ', ' ) ; const modelsMethodImplementation = `final Set<Class<? extends Model>> modifiableSet = new HashSet<>(
      Arrays.<Class<? extends Model>>asList(${ classList })
    );

    return Immutable.of(modifiableSet);
    ` ; loaderClassDeclaration . addClassMethod ( 'models' , 'Set<Class<? extends Model>>' , modelsMethodImplementation , [ ] , [ ] , 'public' , { } , [ 'Override' ] , undefined , modelsMethodDocString ) ; const versionMethodDocString = dedent `
    Get the version of the models.

    @return the version string of the models.
    ` ; loaderClassDeclaration . addClassMethod ( 'version' , 'String' , `return ${ AMPLIFY_MODEL_VERSION };` , [ ] , [ ] , 'public' , { } , [ 'Override' ] , undefined , versionMethodDocString ) ; result . push ( loaderClassDeclaration . string ) ; return result . join ( '\n' ) ; } generateEnums ( ) { const result = [ this . generatePackageName ( ) ] ; Object . entries ( this . getSelectedEnums ( ) ) . forEach ( ( [ name , enumValue ] ) => { const enumDeclaration = new JavaDeclarationBlock ( ) . asKind ( 'enum' ) . access ( 'public' ) . withName ( this . getEnumName ( enumValue ) ) . annotate ( [ 'SuppressWarnings("all")' ] ) . withComment ( 'Auto generated enum from GraphQL schema.' ) ; const body = Object . values ( enumValue . values ) ; enumDeclaration . withBlock ( indentMultiline ( body . join ( ',\n' ) ) ) ; result . push ( enumDeclaration . string ) ; } ) ; return result . join ( '\n' ) ; } generateModelClasses ( ) { const result = [ ] ; Object . entries ( this . getSelectedModels ( ) ) . forEach ( ( [ name , model ] ) => { const modelDeclaration = this . generateModelClass ( model ) ; result . push ( ... [ modelDeclaration ] ) ; } ) ; const packageDeclaration = this . generatePackageHeader ( ) ; return [ packageDeclaration , ... result ] . join ( '\n' ) ; } generateNonModelClasses ( ) { const result = [ ] ; Object . entries ( this . getSelectedNonModels ( ) ) . forEach ( ( [ name , type ] ) => { const nonModelDeclaration = this . generateNonModelClass ( type ) ; result . push ( ... [ nonModelDeclaration ] ) ; } ) ; const packageDeclaration = this . generatePackageHeader ( false ) ; return [ packageDeclaration , ... result ] . join ( '\n' ) ; } generatePackageName ( ) { return `package ${ GENERATED_PACKAGE_NAME };` ; } generateModelClass ( model ) { const classDeclarationBlock = new JavaDeclarationBlock ( ) . asKind ( 'class' ) . access ( 'public' ) . withName ( this . getModelName ( model ) ) . implements ( [ 'Model' ] ) . withComment ( `This is an auto generated class representing the ${ model . name } type in your schema.` ) . final ( ) ; const annotations = this . generateModelAnnotations ( model ) ; classDeclarationBlock . annotate ( annotations ) ; const queryFields = this . getWritableFields ( model ) ; queryFields . forEach ( field => this . generateQueryFields ( model , field , classDeclarationBlock ) ) ; const nonConnectedFields = this . getNonConnectedField ( model ) ; model . fields . forEach ( field => { const value = nonConnectedFields . includes ( field ) ? '' : 'null' ; this . generateModelField ( field , value , classDeclarationBlock ) ; } ) ; this . generateStepBuilderInterfaces ( model ) . forEach ( ( builderInterface ) => { classDeclarationBlock . nestedClass ( builderInterface ) ; } ) ; this . generateBuilderClass ( model , classDeclarationBlock ) ; this . generateCopyOfBuilderClass ( model , classDeclarationBlock ) ; this . generateGetters ( model , classDeclarationBlock ) ; this . generateConstructor ( model , classDeclarationBlock ) ; this . generateEqualsMethod ( model , classDeclarationBlock ) ; this . generateHashCodeMethod ( model , classDeclarationBlock ) ; this . generateToStringMethod ( model , classDeclarationBlock ) ; this . generateBuilderMethod ( model , classDeclarationBlock ) ; this . generateJustIdMethod ( model , classDeclarationBlock ) ; this . generateCopyOfBuilderMethod ( model , classDeclarationBlock ) ; return classDeclarationBlock . string ; } generateNonModelClass ( nonModel ) { const classDeclarationBlock = new JavaDeclarationBlock ( ) . asKind ( 'class' ) . access ( 'public' ) . withName ( this . getModelName ( nonModel ) ) . withComment ( `This is an auto generated class representing the ${ nonModel . name } type in your schema.` ) . final ( ) ; const nonConnectedFields = this . getNonConnectedField ( nonModel ) ; nonModel . fields . forEach ( field => { const value = nonConnectedFields . includes ( field ) ? '' : 'null' ; this . generateNonModelField ( field , value , classDeclarationBlock ) ; } ) ; this . generateStepBuilderInterfaces ( nonModel , false ) . forEach ( ( builderInterface ) => { classDeclarationBlock . nestedClass ( builderInterface ) ; } ) ; this . generateBuilderClass ( nonModel , classDeclarationBlock , false ) ; this . generateCopyOfBuilderClass ( nonModel , classDeclarationBlock , false ) ; this . generateGetters ( nonModel , classDeclarationBlock ) ; this . generateConstructor ( nonModel , classDeclarationBlock ) ; this . generateEqualsMethod ( nonModel , classDeclarationBlock ) ; this . generateHashCodeMethod ( nonModel , classDeclarationBlock ) ; this . generateBuilderMethod ( nonModel , classDeclarationBlock , false ) ; this . generateCopyOfBuilderMethod ( nonModel , classDeclarationBlock ) ; return classDeclarationBlock . string ; } protected generatePackageHeader ( isModel = true ) { let baseImports ; if ( isModel ) { if ( this . usingAuth ) { baseImports = MODEL_AUTH_CLASS_IMPORT_PACKAGES ; } else { baseImports = MODEL_CLASS_IMPORT_PACKAGES ; } } else { baseImports = NON_MODEL_CLASS_IMPORT_PACKAGES ; } const imports = this . generateImportStatements ( [ ... Array . from ( this . additionalPackages ) , '' , ... baseImports ] ) ; return [ this . generatePackageName ( ) , '' , imports ] . join ( '\n' ) ; } param packages returns protected generateImportStatements ( packages ) { return packages . map ( pkg => ( pkg ? `import ${ pkg };` : '' ) ) . join ( '\n' ) ; } /**
     * Add query field used for construction of conditions by SyncEngine
     */ protected generateQueryFields ( model , field , classDeclarationBlock ) { const queryFieldName = constantCase ( field . name ) ; const fieldName = field . connectionInfo && field . connectionInfo . kind === CodeGenConnectionType . BELONGS_TO ? field . connectionInfo . targetName : this . getFieldName ( field ) ; classDeclarationBlock . addClassMember ( queryFieldName , 'QueryField' , `field("${ this . getModelName ( model ) }", "${ fieldName }")` , [ ] , 'public' , { final : true , static : true , } ) ; } param field param classDeclarationBlock protected generateModelField ( field , value , classDeclarationBlock ) { const annotations = this . generateFieldAnnotations ( field ) ; const fieldType = this . getNativeType ( field ) ; const fieldName = this . getFieldName ( field ) ; classDeclarationBlock . addClassMember ( fieldName , fieldType , value , annotations , 'private' , { final : ! field . isReadOnly , } ) ; } /**
     * Add non field members of the non model class
     */ protected generateNonModelField ( field , value , classDeclarationBlock ) { const fieldType = this . getNativeType ( field ) ; const fieldName = this . getFieldName ( field ) ; classDeclarationBlock . addClassMember ( fieldName , fieldType , value , [ ] , 'private' , { final : true , } ) ; } /**
     * Generate step builder interfaces for each non-null field in the model
     *
     */ protected generateStepBuilderInterfaces ( model , isModel = true ) { 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 ) => ! ( isModel && this . READ_ONLY_FIELDS . includes ( field . name ) ) ) ; 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 methodName = this . getStepFunctionName ( field ) ; const argumentType = this . getNativeType ( field ) ; const argumentName = this . getStepFunctionArgumentName ( field ) ; const interfaceDeclaration = new JavaDeclarationBlock ( ) . asKind ( 'interface' ) . withName ( interfaceName ) . access ( 'public' ) ; interfaceDeclaration . withBlock ( indent ( `${ this . getStepInterfaceName ( returnType ) } ${ methodName }(${ argumentType } ${ argumentName });` ) ) ; return interfaceDeclaration ; } ) ; const builder = new JavaDeclarationBlock ( ) . asKind ( 'interface' ) . withName ( this . getStepInterfaceName ( 'Build' ) ) . access ( 'public' ) ; const builderBody = [ ] ; builderBody . push ( `${ this . getModelName ( model ) } build();` ) ; if ( isModel ) { builderBody . push ( `${ this . getStepInterfaceName ( 'Build' ) } 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 });` ) ; } ) ; builder . withBlock ( indentMultiline ( builderBody . join ( '\n' ) ) ) ; return [ ... interfaces , builder ] ; } param model returns protected generateBuilderClass ( model , classDeclaration , isModel = true ) { 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 ) => ! ( isModel && this . READ_ONLY_FIELDS . includes ( field . name ) ) ) ; const stepInterfaces = stepFields . map ( ( field ) => this . getStepInterfaceName ( field . name ) ) ; const builderClassDeclaration = new JavaDeclarationBlock ( ) . access ( 'public' ) . static ( ) . asKind ( 'class' ) . withName ( 'Builder' ) . implements ( [ ... stepInterfaces , this . getStepInterfaceName ( 'Build' ) ] ) ; [ ... nonNullableFields , ... nullableFields ] . forEach ( ( field ) => { const fieldName = this . getFieldName ( field ) ; builderClassDeclaration . addClassMember ( fieldName , this . getNativeType ( field ) , '' , undefined , 'private' ) ; } ) ; const buildImplementation = isModel ? [ `String id = this.id != null ? this.id : UUID.randomUUID().toString();` , '' ] : [ '' ] ; const buildParams = this . getWritableFields ( model ) . map ( field => this . getFieldName ( field ) ) . join ( ',\n' ) ; buildImplementation . push ( `return new ${ this . getModelName ( model ) }(\n${ indentMultiline ( buildParams ) });` ) ; builderClassDeclaration . addClassMethod ( 'build' , this . getModelName ( model ) , indentMultiline ( buildImplementation . join ( '\n' ) ) , undefined , [ ] , 'public' , { } , [ 'Override' ] ) ; stepFields . forEach ( ( field , idx , fields ) => { 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 argumentType = this . getNativeType ( field ) ; const argumentName = this . getStepFunctionArgumentName ( field ) ; const body = [ `Objects.requireNonNull(${ argumentName });` , `this.${ fieldName } = ${ argumentName };` , `return this;` ] . join ( '\n' ) ; builderClassDeclaration . addClassMethod ( methodName , returnType , indentMultiline ( body ) , [ { name : argumentName , type : argumentType } ] , [ ] , 'public' , { } , [ 'Override' ] ) ; } ) ; nullableFields . forEach ( ( field ) => { const fieldName = this . getFieldName ( field ) ; const methodName = this . getStepFunctionName ( field ) ; const returnType = this . getStepInterfaceName ( 'Build' ) ; const argumentType = this . getNativeType ( field ) ; const argumentName = this . getStepFunctionArgumentName ( field ) ; const body = [ `this.${ fieldName } = ${ argumentName };` , `return this;` ] . join ( '\n' ) ; builderClassDeclaration . addClassMethod ( methodName , returnType , indentMultiline ( body ) , [ { name : argumentName , type : argumentType } ] , [ ] , 'public' , { } , [ 'Override' ] ) ; } ) ; if ( isModel ) { const idBuildStepBody = dedent `this.id = id;
    return this;` ; const idComment = dedent `
    @param id id
    @return Current Builder instance, for fluent method chaining` ; builderClassDeclaration . addClassMethod ( 'id' , this . getStepInterfaceName ( 'Build' ) , indentMultiline ( idBuildStepBody ) , [ { name : 'id' , type : 'String' } ] , [ ] , 'public' , { } , [ ] , [ ] , idComment ) ; } classDeclaration . nestedClass ( builderClassDeclaration ) ; } param model param classDeclaration protected generateCopyOfBuilderClass ( model , classDeclaration , isModel = true ) { const builderName = 'CopyOfBuilder' ; const copyOfBuilderClassDeclaration = new JavaDeclarationBlock ( ) . access ( 'public' ) . final ( ) . asKind ( 'class' ) . withName ( builderName ) . extends ( [ 'Builder' ] ) ; const nonNullableFields = this . getWritableFields ( model ) . filter ( field => ! field . isNullable ) . filter ( f => ( isModel ? f . name !== 'id' : true ) ) ; const nullableFields = this . getWritableFields ( model ) . filter ( field => field . isNullable ) ; const constructorArguments = this . getWritableFields ( model ) . map ( field => ( { name : this . getStepFunctionArgumentName ( field ) , type : this . getNativeType ( field ) , } ) ) ; const stepBuilderInvocation = [ ... nonNullableFields , ... nullableFields ] . map ( field => { const methodName = this . getStepFunctionName ( field ) ; const argumentName = this . getStepFunctionArgumentName ( field ) ; return `.${ methodName }(${ argumentName })` ; } ) ; const invocations = stepBuilderInvocation . length === 0 ? '' : [ 'super' , indentMultiline ( stepBuilderInvocation . join ( '\n' ) ) . trim ( ) , ';' ] . join ( '' ) ; const body = [ ... ( isModel ? [ 'super.id(id);' ] : [ ] ) , invocations ] . join ( '\n' ) ; copyOfBuilderClassDeclaration . addClassMethod ( builderName , null , body , constructorArguments , [ ] , 'private' ) ; [ ... nonNullableFields , ... nullableFields ] . forEach ( field => { const methodName = this . getStepFunctionName ( field ) ; const argumentName = this . getStepFunctionArgumentName ( field ) ; const argumentType = this . getNativeType ( field ) ; const implementation = `return (${ builderName }) super.${ methodName }(${ argumentName });` ; copyOfBuilderClassDeclaration . addClassMethod ( methodName , builderName , implementation , [ { name : argumentName , type : argumentType , } , ] , [ ] , 'public' , { } , [ 'Override' ] ) ; } ) ; classDeclaration . nestedClass ( copyOfBuilderClassDeclaration ) ; } /**
     * adds a copyOfBuilder method to the Model class. This method is used to create a copy of the model to mutate it
     */ protected generateCopyOfBuilderMethod ( model , classDeclaration ) { const args = indentMultiline ( this . getWritableFields ( model ) . map ( field => this . getFieldName ( field ) ) . join ( ',\n' ) ) . trim ( ) ; const methodBody = `return new CopyOfBuilder(${ args });` ; classDeclaration . addClassMethod ( 'copyOfBuilder' , 'CopyOfBuilder' , methodBody , [ ] , [ ] , 'public' ) ; } param model param declarationsBlock protected generateGetters ( model , declarationsBlock ) { model . fields . forEach ( ( field ) => { const fieldName = this . getFieldName ( field ) ; const returnType = this . getNativeType ( field ) ; const methodName = this . getFieldGetterName ( field ) ; const body = indent ( `return ${ fieldName };` ) ; declarationsBlock . addClassMethod ( methodName , returnType , body , undefined , undefined , 'public' ) ; } ) ; } param field protected getFieldGetterName ( field ) { return `get${ pascalCase ( field . name ) }` ; } param field protected getStepFunctionName ( field ) { return camelCase ( field . name ) ; } param field protected getStepFunctionArgumentName ( field ) { return camelCase ( field . name ) ; } param model param declarationsBlock protected generateConstructor ( model , declarationsBlock ) { const name = this . getModelName ( model ) ; const body = this . getWritableFields ( model ) . map ( ( field ) => { const fieldName = this . getFieldName ( field ) ; return `this.${ fieldName } = ${ fieldName };` ; } ) . join ( '\n' ) ; const constructorArguments = this . getWritableFields ( model ) . map ( field => ( { name : this . getFieldName ( field ) , type : this . getNativeType ( field ) , } ) ) ; declarationsBlock . addClassMethod ( name , null , body , constructorArguments , undefined , 'private' ) ; } protected getNativeType ( field ) { const nativeType = super . getNativeType ( field ) ; if ( Object . keys ( JAVA_TYPE_IMPORT_MAP ) . includes ( nativeType ) ) { this . additionalPackages . add ( JAVA_TYPE_IMPORT_MAP [ nativeType ] ) ; } return nativeType ; } param model param declarationBlock protected generateEqualsMethod ( model , declarationBlock ) { const paramName = 'obj' ; const className = this . getModelName ( model ) ; const instanceName = camelCase ( model . name ) ; const body = [ `if (this == ${ paramName }) {` , '  return true;' , `} else if(${ paramName } == null || getClass() != ${ paramName }.getClass()) {` , '  return false;' , '} else {' , ] ; body . push ( `${ className } ${ instanceName } = (${ className }) ${ paramName };` ) ; const propCheck = indentMultiline ( this . getNonConnectedField ( model ) . map ( field => { const getterName = this . getFieldGetterName ( field ) ; return `ObjectsCompat.equals(${ getterName }(), ${ instanceName }.${ getterName }())` ; } ) . join ( ' &&\n' ) , 4 ) . trim ( ) ; body . push ( `return ${ propCheck };` ) ; body . push ( '}' ) ; declarationBlock . addClassMethod ( 'equals' , 'boolean' , indentMultiline ( body . join ( '\n' ) ) , [ { name : paramName , type : 'Object' } ] , [ ] , 'public' , { } , [ 'Override' ] ) ; } protected generateHashCodeMethod ( model , declarationBlock ) { const body = [ 'return new StringBuilder()' , ... this . getNonConnectedField ( model ) . map ( field => `.append(${ this . getFieldGetterName ( field ) }())` ) , '.toString()' , '.hashCode();' , ] . join ( '\n' ) ; declarationBlock . addClassMethod ( 'hashCode' , 'int' , indentMultiline ( body ) . trimLeft ( ) , [ ] , [ ] , 'public' , { } , [ 'Override' ] ) ; } protected generateToStringMethod ( model , declarationBlock ) { const fields = this . getNonConnectedField ( model ) . map ( field => { const fieldName = this . getFieldName ( field ) ; const fieldGetterName = this . getFieldGetterName ( field ) ; return '"' + fieldName + '=" + String.valueOf(' + fieldGetterName + '())' ; } ) ; const body = [ 'return new StringBuilder()' , `.append("${ this . getModelName ( model ) } {")` , fields . map ( ( field , index ) => { let fieldDelimiter = '' ; if ( fields . length - index - 1 !== 0 ) { fieldDelimiter = ' + ", "' ; } return '.append(' + field + fieldDelimiter + ')' ; } ) . join ( '\n' ) , '.append("}")' , '.toString();' , ] ; declarationBlock . addClassMethod ( 'toString' , 'String' , indentMultiline ( body . join ( '\n' ) ) . trimLeft ( ) , [ ] , [ ] , 'public' , { } , [ 'Override' ] ) ; } param model param classDeclaration protected generateBuilderMethod ( model , classDeclaration , isModel = true ) { const requiredFields = this . getWritableFields ( model ) . filter ( field => ! field . isNullable && ! ( isModel && this . READ_ONLY_FIELDS . includes ( field . name ) ) ) ; const returnType = requiredFields . length ? this . getStepInterfaceName ( requiredFields [ 0 ] . name ) : this . getStepInterfaceName ( 'Build' ) ; classDeclaration . addClassMethod ( 'builder' , returnType , indentMultiline ( `return new Builder();` ) , [ ] , [ ] , 'public' , { static : true } , [ ] ) ; } param nextFieldName returns private getStepInterfaceName ( nextFieldName ) { return `${ pascalCase ( nextFieldName ) }Step` ; } protected generateModelAnnotations ( model ) { const annotations = model . directives . map ( directive => { switch ( directive . name ) { case 'model' : const modelArgs = [ ] ; const authDirectives = model . directives . filter ( d => d . name === 'auth' ) as AuthDirective [ ] ; const authRules = this . generateAuthRules ( authDirectives ) ; modelArgs . push ( `pluralName = "${ this . pluralizeModelName ( model ) }"` ) ; if ( authRules . length ) { this . usingAuth = true ; modelArgs . push ( `authRules = ${ authRules }` ) ; } return `ModelConfig(${ modelArgs . join ( ', ' ) })` ; case 'key' : const keyArgs = [ ] ; keyArgs . push ( `name = "${ directive . arguments . name }"` ) ; keyArgs . push ( `fields = {${ ( directive . arguments . fields as string [ ] ) . map ( ( f ) => `"${ f }"` ) . join ( ',' ) }}` ) ; return `Index(${ keyArgs . join ( ', ' ) })` ; default : break ; } return '' ; } ) ; return [ 'SuppressWarnings("all")' , ... annotations ] . filter ( annotation => annotation ) ; } protected generateAuthRules ( authDirectives ) { const operationMapping = { create : 'ModelOperation.CREATE' , read : 'ModelOperation.READ' , update : 'ModelOperation.UPDATE' , delete : 'ModelOperation.DELETE' , } ; const rules = [ ] ; authDirectives . forEach ( directive => { directive . arguments ?. rules . forEach ( rule => { const authRule = [ ] ; switch ( rule . allow ) { case AuthStrategy . owner : authRule . push ( 'allow = AuthStrategy.OWNER' ) ; authRule . push ( `ownerField = "${ rule . ownerField }"` ) ; authRule . push ( `identityClaim = "${ rule . identityClaim }"` ) ; break ; case AuthStrategy . private : authRule . push ( 'allow = AuthStrategy.PRIVATE' ) ; break ; case AuthStrategy . public : authRule . push ( 'allow = AuthStrategy.PUBLIC' ) ; break ; case AuthStrategy . groups : authRule . push ( 'allow = AuthStrategy.GROUPS' ) ; authRule . push ( `groupClaim = "${ rule . groupClaim }"` ) ; if ( rule . groups ) { authRule . push ( `groups = { ${ rule . groups ?. map ( group => `"${ group }"` ) . join ( ', ' ) } }` ) ; } else { authRule . push ( `groupsField = "${ rule . groupField }"` ) ; } break ; default : printWarning ( `Model has auth with authStrategy ${ rule . allow } of which is not yet supported` ) ; return ; } if ( rule . provider != null ) { authRule . push ( `provider = "${ rule . provider }"` ) ; } authRule . push ( `operations = { ${ rule . operations ?. map ( op => operationMapping [ op ] ) . join ( ', ' ) } }` ) ; rules . push ( `@AuthRule(${ authRule . join ( ', ' ) })` ) ; } ) ; } ) ; if ( rules . length ) { return [ '{' , `${ indentMultiline ( rules . join ( ',\n' ) ) }` , '}' ] . join ( '\n' ) ; } return '' ; } protected generateFieldAnnotations ( field ) { const annotations = [ ] ; annotations . push ( this . generateModelFieldAnnotation ( field ) ) ; annotations . push ( this . generateConnectionAnnotation ( field ) ) ; return annotations . filter ( annotation => annotation ) ; } protected generateModelFieldAnnotation ( field ) { const authDirectives = field . directives . filter ( d => d . name === 'auth' ) as AuthDirective [ ] ; const authRules = this . generateAuthRules ( authDirectives ) ; if ( authRules . length ) { this . usingAuth = true ; } const annotationArgs = [ `targetType="${ field . type }"` , this . isRequiredField ( field ) ? 'isRequired = true' : '' , authRules . length ? `authRules = ${ authRules }` : '' , field . isReadOnly ? 'isReadOnly = true' : '' , ] . filter ( arg => arg ) ; return `ModelField${ annotationArgs . length ? `(${ annotationArgs . join ( ', ' ) })` : '' }` ; } protected generateConnectionAnnotation ( field ) { if ( ! field . connectionInfo ) return '' ; const { connectionInfo } = field ; this . additionalPackages . add ( CONNECTION_RELATIONSHIP_IMPORTS [ connectionInfo . kind ] ) ; let connectionDirectiveName = '' ; const connectionArguments = [ ] ; switch ( connectionInfo . kind ) { case CodeGenConnectionType . HAS_ONE : connectionDirectiveName = 'HasOne' ; connectionArguments . push ( `associatedWith = "${ this . getFieldName ( connectionInfo . associatedWith ) }"` ) ; break ; case CodeGenConnectionType . HAS_MANY : connectionDirectiveName = 'HasMany' ; connectionArguments . push ( `associatedWith = "${ this . getFieldName ( connectionInfo . associatedWith ) }"` ) ; break ; case CodeGenConnectionType . BELONGS_TO : connectionDirectiveName = 'BelongsTo' ; connectionArguments . push ( `targetName = "${ connectionInfo . targetName }"` ) ; break ; } connectionArguments . push ( `type = ${ this . getModelName ( connectionInfo . connectedModel ) }.class` ) ; return `${ connectionDirectiveName }${ connectionArguments . length ? `(${ connectionArguments . join ( ', ' ) })` : '' }` ; } protected generateJustIdMethod ( model , classDeclaration ) { const returnType = this . getModelName ( model ) ; const comment = dedent `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` ; const initArgs = indentMultiline ( [ 'id' , ... new Array ( this . getWritableFields ( model ) . length - 1 ) . fill ( 'null' ) ] . join ( ',\n' ) ) ; const initBlock = `return new ${ returnType }(\n${ initArgs }\n);` ; classDeclaration . addClassMethod ( 'justId' , returnType , initBlock , [ { name : 'id' , type : 'String' } ] , [ ] , 'public' , { static : true } , [ ] , [ ] , comment ) ; } param model protected getNonConnectedField ( model ) { return model . fields . filter ( f => { if ( ! f . connectionInfo ) return true ; if ( f . connectionInfo . kind == CodeGenConnectionType . BELONGS_TO ) { return true ; } } ) ; } protected getWritableFields ( model ) { return this . getNonConnectedField ( model ) . filter ( f => ! f . isReadOnly ) ; } }