import { anyTypeIssueAnnotation , nullTypeIssueAnnotation } from "../Annotation" ; import { ConvenienceRenderer , ForbiddenWordsInfo } from "../ConvenienceRenderer" ; import { DependencyName , funPrefixNamer , Name , Namer } from "../Naming" ; import { RenderContext } from "../Renderer" ; import { BooleanOption , EnumOption , getOptionValues , Option , OptionValues , StringOption } from "../RendererOptions" ; import { maybeAnnotated , Sourcelike } from "../Source" ; import { acronymOption , acronymStyle , AcronymStyleOptions } from "../support/Acronyms" ; import { allLowerWordStyle , allUpperWordStyle , capitalize , combineWords , escapeNonPrintableMapper , firstUpperWordStyle , isAscii , isDigit , isLetter , splitIntoWords , standardUnicodeHexEscape , utf16ConcatMap , utf16LegalizeCharacters , } from "../support/Strings" ; import { assert , assertNever , defined , panic } from "../support/Support" ; import { TargetLanguage } from "../TargetLanguage" ; import { ArrayType , ClassProperty , ClassType , EnumType , MapType , Type , TypeKind , UnionType } from "../Type" ; import { directlyReachableSingleNamedType , matchType , nullableFromUnion , removeNullFromUnion } from "../TypeUtils" ; import { StringTypeMapping , TransformedStringTypeKind , PrimitiveStringTypeKind } from ".." ; export const javaOptions = { useList : new EnumOption ( "array-type" , "Use T[] or List<T>" , [ [ "array" , false ] , [ "list" , true ] , ] , "array" ) , justTypes : new BooleanOption ( "just-types" , "Plain types only" , false ) , dateTimeProvider : new EnumOption ( "datetime-provider" , "Date time provider type" , [ [ "java8" , "java8" ] , [ "legacy" , "legacy" ] ] , "java8" ) , acronymStyle : acronymOption ( AcronymStyleOptions . Pascal ) , packageName : new StringOption ( "package" , "Generated package name" , "NAME" , "io.quicktype" ) , lombok : new BooleanOption ( "lombok" , "Use lombok" , false , "primary" ) , lombokCopyAnnotations : new BooleanOption ( "lombok-copy-annotations" , "Copy accessor annotations" , true , "secondary" ) , } ; export class JavaTargetLanguage extends TargetLanguage { constructor ( ) { super ( "Java" , [ "java" ] , "java" ) ; } protected getOptions ( ) { return [ javaOptions . useList , javaOptions . justTypes , javaOptions . dateTimeProvider , javaOptions . acronymStyle , javaOptions . packageName , javaOptions . lombok , javaOptions . lombokCopyAnnotations , ] ; } get supportsUnionsWithBothNumberTypes ( ) : boolean { return true ; } protected makeRenderer ( renderContext , untypedOptionValues ) { const options = getOptionValues ( javaOptions , untypedOptionValues ) ; if ( options . justTypes ) { return new JavaRenderer ( this , renderContext , options ) ; } return new JacksonRenderer ( this , renderContext , options ) ; } get stringTypeMapping ( ) : StringTypeMapping { const mapping = new Map ( ) ; mapping . set ( "date" , "date" ) ; mapping . set ( "time" , "time" ) ; mapping . set ( "date-time" , "date-time" ) ; mapping . set ( "uuid" , "uuid" ) ; return mapping ; } } const javaKeywords = [ "Object" , "Class" , "System" , "Long" , "Double" , "Boolean" , "String" , "Map" , "UUID" , "Exception" , "IOException" , "Override" , "abstract" , "continue" , "for" , "new" , "switch" , "assert" , "default" , "goto" , "package" , "synchronized" , "boolean" , "do" , "if" , "private" , "this" , "break" , "double" , "implements" , "protected" , "throw" , "byte" , "else" , "import" , "public" , "throws" , "case" , "enum" , "instanceof" , "return" , "transient" , "catch" , "extends" , "int" , "short" , "try" , "char" , "final" , "interface" , "static" , "void" , "class" , "finally" , "long" , "strictfp" , "volatile" , "const" , "float" , "native" , "super" , "while" , "null" , "false" , "true" , ] ; export const stringEscape = utf16ConcatMap ( escapeNonPrintableMapper ( isAscii , standardUnicodeHexEscape ) ) ; function isStartCharacter ( codePoint ) { if ( codePoint === 0x5f ) return true ; return isAscii ( codePoint ) && isLetter ( codePoint ) ; } function isPartCharacter ( codePoint ) { return isStartCharacter ( codePoint ) || ( isAscii ( codePoint ) && isDigit ( codePoint ) ) ; } const legalizeName = utf16LegalizeCharacters ( isPartCharacter ) ; export function javaNameStyle ( startWithUpper , upperUnderscore , original , acronymsStyle = allUpperWordStyle ) { const words = splitIntoWords ( original ) ; return combineWords ( words , legalizeName , upperUnderscore ? allUpperWordStyle : startWithUpper ? firstUpperWordStyle : allLowerWordStyle , upperUnderscore ? allUpperWordStyle : firstUpperWordStyle , upperUnderscore || startWithUpper ? allUpperWordStyle : allLowerWordStyle , acronymsStyle , upperUnderscore ? "_" : "" , isStartCharacter ) ; } abstract class JavaDateTimeProvider { constructor ( protected readonly _renderer , protected readonly _className ) { } abstract keywords : string [ ] ; abstract dateTimeImports : string [ ] ; abstract dateImports : string [ ] ; abstract timeImports : string [ ] ; abstract converterImports : string [ ] ; abstract dateTimeType : string ; abstract dateType : string ; abstract timeType : string ; abstract dateTimeJacksonAnnotations : string [ ] ; abstract dateJacksonAnnotations : string [ ] ; abstract timeJacksonAnnotations : string [ ] ; abstract emitDateTimeConverters ( ) ; public shouldEmitDateTimeConverter : boolean = true ; public shouldEmitTimeConverter : boolean = true ; public shouldEmitDateConverter : boolean = true ; abstract convertStringToDateTime ( variable ) ; abstract convertStringToTime ( variable ) ; abstract convertStringToDate ( variable ) ; abstract convertDateTimeToString ( variable ) ; abstract convertTimeToString ( variable ) ; abstract convertDateToString ( variable ) ; } class Java8DateTimeProvider extends JavaDateTimeProvider { keywords = [ "LocalDate" , "OffsetDateTime" , "OffsetTime" , "ZoneOffset" , "ZonedDateTime" , "DateTimeFormatter" , "DateTimeFormatterBuilder" , "ChronoField" ] ; dateTimeImports : string [ ] = [ "java.time.OffsetDateTime" ] ; dateImports : string [ ] = [ "java.time.LocalDate" ] ; timeImports : string [ ] = [ "java.time.OffsetTime" ] ; converterImports : string [ ] = [ "java.time.LocalDate" , "java.time.OffsetDateTime" , "java.time.OffsetTime" , "java.time.ZoneOffset" , "java.time.ZonedDateTime" , "java.time.format.DateTimeFormatter" , "java.time.format.DateTimeFormatterBuilder" , "java.time.temporal.ChronoField" ] ; dateTimeType : string = "OffsetDateTime" ; dateType : string = "LocalDate" ; timeType : string = "OffsetTime" ; dateTimeJacksonAnnotations : string [ ] = [ ] ; dateJacksonAnnotations : string [ ] = [ ] ; timeJacksonAnnotations : string [ ] = [ ] ; emitDateTimeConverters ( ) { this . _renderer . ensureBlankLine ( ) ; this . _renderer . emitLine ( "private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()" ) ; this . _renderer . indent ( ( ) => this . _renderer . indent ( ( ) => { this . _renderer . emitLine ( ".appendOptional(DateTimeFormatter.ISO_DATE_TIME)" ) ; this . _renderer . emitLine ( ".appendOptional(DateTimeFormatter.ISO_OFFSET_DATE_TIME)" ) ; this . _renderer . emitLine ( ".appendOptional(DateTimeFormatter.ISO_INSTANT)" ) ; this . _renderer . emitLine ( '.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SX"))' ) ; this . _renderer . emitLine ( '.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssX"))' ) ; this . _renderer . emitLine ( '.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))' ) ; this . _renderer . emitLine ( ".toFormatter()" ) ; this . _renderer . emitLine ( ".withZone(ZoneOffset.UTC);" ) ; } ) ) ; this . _renderer . ensureBlankLine ( ) ; this . _renderer . emitBlock ( "public static OffsetDateTime parseDateTimeString(String str)" , ( ) => { this . _renderer . emitLine ( "return ZonedDateTime.from(Converter.DATE_TIME_FORMATTER.parse(str)).toOffsetDateTime();" ) ; } ) ; this . _renderer . ensureBlankLine ( ) ; this . _renderer . emitLine ( "private static final DateTimeFormatter TIME_FORMATTER = new DateTimeFormatterBuilder()" ) ; this . _renderer . indent ( ( ) => this . _renderer . indent ( ( ) => { this . _renderer . emitLine ( ".appendOptional(DateTimeFormatter.ISO_TIME)" ) ; this . _renderer . emitLine ( ".appendOptional(DateTimeFormatter.ISO_OFFSET_TIME)" ) ; this . _renderer . emitLine ( ".parseDefaulting(ChronoField.YEAR, 2020)" ) ; this . _renderer . emitLine ( ".parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)" ) ; this . _renderer . emitLine ( ".parseDefaulting(ChronoField.DAY_OF_MONTH, 1)" ) ; this . _renderer . emitLine ( ".toFormatter()" ) ; this . _renderer . emitLine ( ".withZone(ZoneOffset.UTC);" ) ; } ) ) ; this . _renderer . ensureBlankLine ( ) ; this . _renderer . emitBlock ( "public static OffsetTime parseTimeString(String str)" , ( ) => { this . _renderer . emitLine ( "return ZonedDateTime.from(Converter.TIME_FORMATTER.parse(str)).toOffsetDateTime().toOffsetTime();" ) ; } ) ; } convertStringToDateTime ( variable ) { return [ this . _className , ".parseDateTimeString(" , variable , ")" ] ; } convertStringToTime ( variable ) { return [ this . _className , ".parseTimeString(" , variable , ")" ] ; } convertStringToDate ( variable ) { return [ "LocalDate.parse(" , variable , ")" ] ; } convertDateTimeToString ( variable ) { return [ variable , ".format(java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME)" ] ; } convertTimeToString ( variable ) { return [ variable , ".format(java.time.format.DateTimeFormatter.ISO_OFFSET_TIME)" ] ; } convertDateToString ( variable ) { return [ variable , ".format(java.time.format.DateTimeFormatter.ISO_DATE)" ] ; } } class JavaLegacyDateTimeProvider extends JavaDateTimeProvider { keywords = [ "SimpleDateFormat" , "Date" , ] ; dateTimeImports : string [ ] = [ "java.util.Date" ] ; dateImports : string [ ] = [ "java.util.Date" ] ; timeImports : string [ ] = [ "java.util.Date" ] ; converterImports : string [ ] = [ "java.util.Date" , "java.text.SimpleDateFormat" , ] ; dateTimeType : string = "Date" ; dateType : string = "Date" ; timeType : string = "Date" ; dateTimeJacksonAnnotations : string [ ] = [ '@JsonFormat(pattern = "yyyy-MM-dd\'T\'HH:mm:ssX", timezone = "UTC")' ] ; dateJacksonAnnotations : string [ ] = [ '@JsonFormat(pattern = "yyyy-MM-dd")' ] ; timeJacksonAnnotations : string [ ] = [ '@JsonFormat(pattern = "HH:mm:ssX", timezone = "UTC")' ] ; emitDateTimeConverters ( ) { this . _renderer . ensureBlankLine ( ) ; this . _renderer . emitLine ( "private static final String[] DATE_TIME_FORMATS = {" ) ; this . _renderer . indent ( ( ) => this . _renderer . indent ( ( ) => { this . _renderer . emitLine ( '"yyyy-MM-dd\'T\'HH:mm:ss.SX",' ) ; this . _renderer . emitLine ( '"yyyy-MM-dd\'T\'HH:mm:ss.S",' ) ; this . _renderer . emitLine ( '"yyyy-MM-dd\'T\'HH:mm:ssX",' ) ; this . _renderer . emitLine ( '"yyyy-MM-dd\'T\'HH:mm:ss",' ) ; this . _renderer . emitLine ( '"yyyy-MM-dd HH:mm:ss.SX",' ) ; this . _renderer . emitLine ( '"yyyy-MM-dd HH:mm:ss.S",' ) ; this . _renderer . emitLine ( '"yyyy-MM-dd HH:mm:ssX",' ) ; this . _renderer . emitLine ( '"yyyy-MM-dd HH:mm:ss",' ) ; this . _renderer . emitLine ( '"HH:mm:ss.SZ",' ) ; this . _renderer . emitLine ( '"HH:mm:ss.S",' ) ; this . _renderer . emitLine ( '"HH:mm:ssZ",' ) ; this . _renderer . emitLine ( '"HH:mm:ss",' ) ; this . _renderer . emitLine ( '"yyyy-MM-dd",' ) ; } ) ) ; this . _renderer . emitLine ( "};" ) ; this . _renderer . ensureBlankLine ( ) ; this . _renderer . emitBlock ( "public static Date parseAllDateTimeString(String str)" , ( ) => { this . _renderer . emitBlock ( "for (String format : DATE_TIME_FORMATS)" , ( ) => { this . _renderer . emitIgnoredTryCatchBlock ( ( ) => { this . _renderer . emitLine ( "return new SimpleDateFormat(format).parse(str);" ) ; } ) ; } ) ; this . _renderer . emitLine ( "return null;" ) ; } ) ; this . _renderer . ensureBlankLine ( ) ; this . _renderer . emitBlock ( "public static String serializeDateTime(Date datetime)" , ( ) => { this . _renderer . emitLine ( 'return new SimpleDateFormat("yyyy-MM-dd\'T\'hh:mm:ssZ").format(datetime);' ) ; } ) ; this . _renderer . ensureBlankLine ( ) ; this . _renderer . emitBlock ( "public static String serializeDate(Date datetime)" , ( ) => { this . _renderer . emitLine ( 'return new SimpleDateFormat("yyyy-MM-dd").format(datetime);' ) ; } ) ; this . _renderer . ensureBlankLine ( ) ; this . _renderer . emitBlock ( "public static String serializeTime(Date datetime)" , ( ) => { this . _renderer . emitLine ( 'return new SimpleDateFormat("hh:mm:ssZ").format(datetime);' ) ; } ) ; } shouldEmitTimeConverter = false ; shouldEmitDateConverter = false ; convertStringToDateTime ( variable ) { return [ this . _className , ".parseAllDateTimeString(" , variable , ")" ] ; } convertStringToTime ( variable ) { return [ this . _className , ".parseAllDateTimeString(" , variable , ")" ] ; } convertStringToDate ( variable ) { return [ this . _className , ".parseAllDateTimeString(" , variable , ")" ] ; } convertDateTimeToString ( variable ) { return [ this . _className , ".serializeDateTime(" , variable , ")" ] ; } convertTimeToString ( variable ) { return [ this . _className , ".serializeTime(" , variable , ")" ] ; } convertDateToString ( variable ) { return [ this . _className , ".serializeDate(" , variable , ")" ] ; } } export class JavaRenderer extends ConvenienceRenderer { private _currentFilename : string | undefined ; private readonly _gettersAndSettersForPropertyName = new Map < Name , [ Name , Name ] > ( ) ; private _haveEmittedLeadingComments = false ; protected readonly _dateTimeProvider : JavaDateTimeProvider ; protected readonly _converterClassname : string = "Converter" ; protected readonly _converterKeywords : string [ ] = [ ] ; constructor ( targetLanguage , renderContext , protected readonly _options ) { super ( targetLanguage , renderContext ) ; switch ( _options . dateTimeProvider ) { default : case "java8" : this . _dateTimeProvider = new Java8DateTimeProvider ( this , this . _converterClassname ) ; break ; case "legacy" : this . _dateTimeProvider = new JavaLegacyDateTimeProvider ( this , this . _converterClassname ) ; break ; } } protected forbiddenNamesForGlobalNamespace ( ) { const keywords = [ ... javaKeywords , ... this . _converterKeywords , this . _converterClassname , ... this . _dateTimeProvider . keywords ] ; return keywords ; } protected forbiddenForObjectProperties ( _c , _className ) { return { names : [ ] , includeGlobalForbidden : true } ; } protected makeNamedTypeNamer ( ) { return this . getNameStyling ( "typeNamingFunction" ) ; } protected namerForObjectProperty ( ) { return this . getNameStyling ( "propertyNamingFunction" ) ; } protected makeUnionMemberNamer ( ) { return this . getNameStyling ( "propertyNamingFunction" ) ; } protected makeEnumCaseNamer ( ) { return this . getNameStyling ( "enumCaseNamingFunction" ) ; } protected unionNeedsName ( u ) { return nullableFromUnion ( u ) === null ; } protected namedTypeToNameForTopLevel ( type ) { return directlyReachableSingleNamedType ( type ) ; } protected makeNamesForPropertyGetterAndSetter ( _c , _className , _p , _jsonName , name ) { const getterName = new DependencyName ( this . getNameStyling ( "propertyNamingFunction" ) , name . order , ( lookup ) => `get_${ lookup ( name ) }` ) ; const setterName = new DependencyName ( this . getNameStyling ( "propertyNamingFunction" ) , name . order , ( lookup ) => `set_${ lookup ( name ) }` ) ; return [ getterName , setterName ] ; } protected makePropertyDependencyNames ( c , className , p , jsonName , name ) { const getterAndSetterNames = this . makeNamesForPropertyGetterAndSetter ( c , className , p , jsonName , name ) ; this . _gettersAndSettersForPropertyName . set ( name , getterAndSetterNames ) ; return getterAndSetterNames ; } private getNameStyling ( convention ) { const styling = { typeNamingFunction : funPrefixNamer ( "types" , ( n ) => javaNameStyle ( true , false , n , acronymStyle ( this . _options . acronymStyle ) ) ) , propertyNamingFunction : funPrefixNamer ( "properties" , ( n ) => javaNameStyle ( false , false , n , acronymStyle ( this . _options . acronymStyle ) ) ) , enumCaseNamingFunction : funPrefixNamer ( "enum-cases" , ( n ) => javaNameStyle ( true , true , n , acronymStyle ( this . _options . acronymStyle ) ) ) , } ; return styling [ convention ] ; } protected fieldOrMethodName ( methodName , topLevelName ) { if ( this . topLevels . size === 1 ) { return methodName ; } return [ topLevelName , capitalize ( methodName ) ] ; } protected methodName ( prefix , suffix , topLevelName ) { if ( this . topLevels . size === 1 ) { return [ prefix , suffix ] ; } return [ prefix , topLevelName , suffix ] ; } protected decoderName ( topLevelName ) { return this . fieldOrMethodName ( "fromJsonString" , topLevelName ) ; } protected encoderName ( topLevelName ) { return this . fieldOrMethodName ( "toJsonString" , topLevelName ) ; } protected readerGetterName ( topLevelName ) { return this . methodName ( "get" , "ObjectReader" , topLevelName ) ; } protected writerGetterName ( topLevelName ) { return this . methodName ( "get" , "ObjectWriter" , topLevelName ) ; } protected startFile ( basename ) { assert ( this . _currentFilename === undefined , "Previous file wasn't finished" ) ; this . _currentFilename = `${ this . sourcelikeToString ( basename ) }.java` ; this . ensureBlankLine ( ) ; if ( ! this . _haveEmittedLeadingComments && this . leadingComments !== undefined ) { this . emitCommentLines ( this . leadingComments ) ; this . ensureBlankLine ( ) ; this . _haveEmittedLeadingComments = true ; } } protected finishFile ( ) { super . finishFile ( defined ( this . _currentFilename ) ) ; this . _currentFilename = undefined ; } protected emitPackageAndImports ( imports ) { this . emitLine ( "package " , this . _options . packageName , ";" ) ; this . ensureBlankLine ( ) ; for ( const pkg of imports ) { this . emitLine ( "import " , pkg , ";" ) ; } } protected emitFileHeader ( fileName , imports ) { this . startFile ( fileName ) ; this . emitPackageAndImports ( imports ) ; this . ensureBlankLine ( ) ; } public emitDescriptionBlock ( lines ) { this . emitCommentLines ( lines , " * " , "/**" , " */" ) ; } public emitBlock ( line , f ) { this . emitLine ( line , " {" ) ; this . indent ( f ) ; this . emitLine ( "}" ) ; } public emitTryCatch ( main , handler , exception = "Exception" ) { this . emitLine ( "try {" ) ; this . indent ( main ) ; this . emitLine ( "} catch (" , exception , " ex) {" ) ; this . indent ( handler ) ; this . emitLine ( "}" ) ; } public emitIgnoredTryCatchBlock ( f ) { this . emitTryCatch ( f , ( ) => this . emitLine ( "// Ignored" ) ) ; } protected javaType ( reference , t , withIssues = false ) { return matchType < Sourcelike > ( t , ( _anyType ) => maybeAnnotated ( withIssues , anyTypeIssueAnnotation , "Object" ) , ( _nullType ) => maybeAnnotated ( withIssues , nullTypeIssueAnnotation , "Object" ) , ( _boolType ) => ( reference ? "Boolean" : "boolean" ) , ( _integerType ) => ( reference ? "Long" : "long" ) , ( _doubleType ) => ( reference ? "Double" : "double" ) , ( _stringType ) => "String" , ( arrayType ) => { if ( this . _options . useList ) { return [ "List<" , this . javaType ( true , arrayType . items , withIssues ) , ">" ] ; } else { return [ this . javaType ( false , arrayType . items , withIssues ) , "[]" ] ; } } , ( classType ) => this . nameForNamedType ( classType ) , ( mapType ) => [ "Map<String, " , this . javaType ( true , mapType . values , withIssues ) , ">" ] , ( enumType ) => this . nameForNamedType ( enumType ) , ( unionType ) => { const nullable = nullableFromUnion ( unionType ) ; if ( nullable !== null ) return this . javaType ( true , nullable , withIssues ) ; return this . nameForNamedType ( unionType ) ; } , ( transformedStringType ) => { if ( transformedStringType . kind === "time" ) { return this . _dateTimeProvider . timeType ; } if ( transformedStringType . kind === "date" ) { return this . _dateTimeProvider . dateType ; } if ( transformedStringType . kind === "date-time" ) { return this . _dateTimeProvider . dateTimeType ; } if ( transformedStringType . kind === "uuid" ) { return "UUID" ; } return "String" ; } ) ; } protected javaImport ( t ) { return matchType < string [ ] > ( t , ( _anyType ) => [ ] , ( _nullType ) => [ ] , ( _boolType ) => [ ] , ( _integerType ) => [ ] , ( _doubleType ) => [ ] , ( _stringType ) => [ ] , ( arrayType ) => { if ( this . _options . useList ) { return [ ... this . javaImport ( arrayType . items ) , "java.util.List" ] ; } else { return [ ... this . javaImport ( arrayType . items ) ] ; } } , ( _classType ) => [ ] , ( mapType ) => [ ... this . javaImport ( mapType . values ) , "java.util.Map" ] , ( _enumType ) => [ ] , ( unionType ) => { const imports = [ ] ; unionType . members . forEach ( ( type ) => this . javaImport ( type ) . forEach ( ( imp ) => imports . push ( imp ) ) ) ; return imports ; } , ( transformedStringType ) => { if ( transformedStringType . kind === "time" ) { return this . _dateTimeProvider . timeImports ; } if ( transformedStringType . kind === "date" ) { return this . _dateTimeProvider . dateImports ; } if ( transformedStringType . kind === "date-time" ) { return this . _dateTimeProvider . dateTimeImports ; } if ( transformedStringType . kind === "uuid" ) { return [ "java.util.UUID" ] ; } return [ ] ; } ) ; } protected javaTypeWithoutGenerics ( reference , t ) { if ( t instanceof ArrayType ) { if ( this . _options . useList ) { return [ "List<" , this . javaTypeWithoutGenerics ( true , t . items ) , ">" ] ; } else { return [ this . javaTypeWithoutGenerics ( false , t . items ) , "[]" ] ; } } else if ( t instanceof MapType ) { return "Map" ; } else if ( t instanceof UnionType ) { const nullable = nullableFromUnion ( t ) ; if ( nullable !== null ) return this . javaTypeWithoutGenerics ( true , nullable ) ; return this . nameForNamedType ( t ) ; } else { return this . javaType ( reference , t ) ; } } protected emitClassAttributes ( _c , _className ) { if ( this . _options . lombok ) { this . emitLine ( "@lombok.Data" ) ; } } protected annotationsForAccessor ( _c , _className , _propertyName , _jsonName , _p , _isSetter ) { return [ ] ; } protected importsForType ( t ) { if ( t instanceof ClassType ) { return [ ] ; } if ( t instanceof UnionType ) { return [ "java.io.IOException" ] ; } if ( t instanceof EnumType ) { return [ "java.io.IOException" ] ; } return assertNever ( t ) ; } protected importsForClass ( c ) { const imports = [ ] ; this . forEachClassProperty ( c , "none" , ( _name , _jsonName , p ) => { this . javaImport ( p . type ) . forEach ( ( imp ) => imports . push ( imp ) ) ; } ) ; imports . sort ( ) ; return [ ... new Set ( imports ) ] ; } protected importsForUnionMembers ( u ) { const imports = [ ] ; const [ , nonNulls ] = removeNullFromUnion ( u ) ; this . forEachUnionMember ( u , nonNulls , "none" , null , ( _fieldName , t ) => { this . javaImport ( t ) . forEach ( ( imp ) => imports . push ( imp ) ) ; } ) ; imports . sort ( ) ; return [ ... new Set ( imports ) ] ; } protected emitClassDefinition ( c , className ) { let imports = [ ... this . importsForType ( c ) , ... this . importsForClass ( c ) ] ; this . emitFileHeader ( className , imports ) ; this . emitDescription ( this . descriptionForType ( c ) ) ; this . emitClassAttributes ( c , className ) ; this . emitBlock ( [ "public class " , className ] , ( ) => { this . forEachClassProperty ( c , "none" , ( name , jsonName , p ) => { if ( this . _options . lombok && this . _options . lombokCopyAnnotations ) { const getter = this . annotationsForAccessor ( c , className , name , jsonName , p , false ) ; const setter = this . annotationsForAccessor ( c , className , name , jsonName , p , true ) ; if ( getter . length !== 0 ) { this . emitLine ( "@lombok.Getter(onMethod_ = {" + getter . join ( ", " ) + "})" ) ; } if ( setter . length !== 0 ) { this . emitLine ( "@lombok.Setter(onMethod_ = {" + setter . join ( ", " ) + "})" ) ; } } this . emitLine ( "private " , this . javaType ( false , p . type , true ) , " " , name , ";" ) ; } ) ; if ( ! this . _options . lombok ) { this . forEachClassProperty ( c , "leading-and-interposing" , ( name , jsonName , p ) => { this . emitDescription ( this . descriptionForClassProperty ( c , jsonName ) ) ; const [ getterName , setterName ] = defined ( this . _gettersAndSettersForPropertyName . get ( name ) ) ; const rendered = this . javaType ( false , p . type ) ; this . annotationsForAccessor ( c , className , name , jsonName , p , false ) . forEach ( annotation => this . emitLine ( annotation ) ) ; this . emitLine ( "public " , rendered , " " , getterName , "() { return " , name , "; }" ) ; this . annotationsForAccessor ( c , className , name , jsonName , p , true ) . forEach ( annotation => this . emitLine ( annotation ) ) ; this . emitLine ( "public void " , setterName , "(" , rendered , " value) { this." , name , " = value; }" ) ; } ) ; } } ) ; this . finishFile ( ) ; } protected unionField ( u , t , withIssues = false ) { const fieldType = this . javaType ( true , t , withIssues ) ; const fieldName = [ this . nameForUnionMember ( u , t ) , "Value" ] ; return { fieldType , fieldName } ; } protected emitUnionAttributes ( _u , _unionName ) { } protected emitUnionSerializer ( _u , _unionName ) { } protected emitUnionDefinition ( u , unionName ) { const imports = [ ... this . importsForType ( u ) , ... this . importsForUnionMembers ( u ) ] ; this . emitFileHeader ( unionName , imports ) ; this . emitDescription ( this . descriptionForType ( u ) ) ; const [ , nonNulls ] = removeNullFromUnion ( u ) ; this . emitUnionAttributes ( u , unionName ) ; this . emitBlock ( [ "public class " , unionName ] , ( ) => { for ( const t of nonNulls ) { const { fieldType , fieldName } = this . unionField ( u , t , true ) ; this . emitLine ( "public " , fieldType , " " , fieldName , ";" ) ; } this . emitUnionSerializer ( u , unionName ) ; } ) ; this . finishFile ( ) ; } protected emitEnumSerializationAttributes ( _e ) { } protected emitEnumDeserializationAttributes ( _e ) { } protected emitEnumDefinition ( e , enumName ) { this . emitFileHeader ( enumName , this . importsForType ( e ) ) ; this . emitDescription ( this . descriptionForType ( e ) ) ; const caseNames = [ ] ; this . forEachEnumCase ( e , "none" , ( name ) => { if ( caseNames . length > 0 ) caseNames . push ( ", " ) ; caseNames . push ( name ) ; } ) ; caseNames . push ( ";" ) ; this . emitBlock ( [ "public enum " , enumName ] , ( ) => { this . emitLine ( caseNames ) ; this . ensureBlankLine ( ) ; this . emitEnumSerializationAttributes ( e ) ; this . emitBlock ( "public String toValue()" , ( ) => { this . emitLine ( "switch (this) {" ) ; this . indent ( ( ) => { this . forEachEnumCase ( e , "none" , ( name , jsonName ) => { this . emitLine ( "case " , name , ': return "' , stringEscape ( jsonName ) , '";' ) ; } ) ; } ) ; this . emitLine ( "}" ) ; this . emitLine ( "return null;" ) ; } ) ; this . ensureBlankLine ( ) ; this . emitEnumDeserializationAttributes ( e ) ; this . emitBlock ( [ "public static " , enumName , " forValue(String value) throws IOException" ] , ( ) => { this . forEachEnumCase ( e , "none" , ( name , jsonName ) => { this . emitLine ( 'if (value.equals("' , stringEscape ( jsonName ) , '")) return ' , name , ";" ) ; } ) ; this . emitLine ( 'throw new IOException("Cannot deserialize ' , enumName , '");' ) ; } ) ; } ) ; this . finishFile ( ) ; } protected emitSourceStructure ( ) { this . forEachNamedType ( "leading-and-interposing" , ( c , n ) => this . emitClassDefinition ( c , n ) , ( e , n ) => this . emitEnumDefinition ( e , n ) , ( u , n ) => this . emitUnionDefinition ( u , n ) ) ; } } export class JacksonRenderer extends JavaRenderer { constructor ( targetLanguage , renderContext , options ) { super ( targetLanguage , renderContext , options ) ; } protected readonly _converterKeywords : string [ ] = [ "JsonProperty" , "JsonDeserialize" , "JsonDeserializer" , "JsonSerialize" , "JsonSerializer" , "JsonParser" , "JsonProcessingException" , "DeserializationContext" , "SerializerProvider" , ] ; protected emitClassAttributes ( c , _className ) { if ( c . getProperties ( ) . size === 0 ) this . emitLine ( "@JsonAutoDetect(fieldVisibility=JsonAutoDetect.Visibility.NONE)" ) ; super . emitClassAttributes ( c , _className ) ; } protected annotationsForAccessor ( _c , _className , _propertyName , jsonName , p , _isSetter ) { const superAnnotations = super . annotationsForAccessor ( _c , _className , _propertyName , jsonName , p , _isSetter ) ; const annotations = [ ( '@JsonProperty("' + stringEscape ( jsonName ) + '")' ) ] ; switch ( p . type . kind ) { case "date-time" : this . _dateTimeProvider . dateTimeJacksonAnnotations . forEach ( annotation => annotations . push ( annotation ) ) ; break ; case "date" : this . _dateTimeProvider . dateJacksonAnnotations . forEach ( annotation => annotations . push ( annotation ) ) ; break ; case "time" : this . _dateTimeProvider . timeJacksonAnnotations . forEach ( annotation => annotations . push ( annotation ) ) ; break ; default : break ; } return [ ... superAnnotations , ... annotations ] ; } protected importsForType ( t ) { if ( t instanceof ClassType ) { const imports = super . importsForType ( t ) ; imports . push ( "com.fasterxml.jackson.annotation.*" ) ; return imports ; } if ( t instanceof UnionType ) { const imports = super . importsForType ( t ) ; imports . push ( "java.io.IOException" , "com.fasterxml.jackson.core.*" , "com.fasterxml.jackson.databind.*" , "com.fasterxml.jackson.databind.annotation.*" ) ; if ( this . _options . useList ) { imports . push ( "com.fasterxml.jackson.core.type.*" ) ; } return imports ; } if ( t instanceof EnumType ) { const imports = super . importsForType ( t ) ; imports . push ( "com.fasterxml.jackson.annotation.*" ) ; return imports ; } return assertNever ( t ) ; } protected emitUnionAttributes ( _u , unionName ) { this . emitLine ( "@JsonDeserialize(using = " , unionName , ".Deserializer.class)" ) ; this . emitLine ( "@JsonSerialize(using = " , unionName , ".Serializer.class)" ) ; } protected emitUnionSerializer ( u , unionName ) { const stringBasedObjects = [ "uuid" , "time" , "date" , "date-time" ] ; const tokenCase = ( tokenType ) : void => { this . emitLine ( "case " , tokenType , ":" ) ; } ; const emitNullDeserializer = ( ) : void => { this . indent ( ( ) => { tokenCase ( "VALUE_NULL" ) ; this . indent ( ( ) => this . emitLine ( "break;" ) ) ; } ) ; } ; const emitDeserializerCodeForStringObjects = ( fieldName , kind , parseFrom ) : void => { switch ( kind ) { case "date" : this . emitLine ( "value." , fieldName , " = " , this . _dateTimeProvider . convertStringToDate ( parseFrom ) , ";" ) ; break ; case "time" : this . emitLine ( "value." , fieldName , " = " , this . _dateTimeProvider . convertStringToTime ( parseFrom ) , ";" ) ; break ; case "date-time" : this . emitLine ( "value." , fieldName , " = " , this . _dateTimeProvider . convertStringToDateTime ( parseFrom ) , ";" ) ; break ; case "uuid" : this . emitLine ( "value." , fieldName , " = UUID.fromString(" , parseFrom , ");" ) ; break ; default : return panic ( "Requested type isnt an object!" ) ; } } ; const emitDeserializeType = ( t , variableFieldName = "" ) : void => { const { fieldName } = this . unionField ( u , t ) ; const rendered = this . javaTypeWithoutGenerics ( true , t ) ; if ( this . _options . useList && t instanceof ArrayType ) { this . emitLine ( "value." , fieldName , " = jsonParser.readValueAs(new TypeReference<" , rendered , ">() {});" ) ; } else if ( stringBasedObjects . some ( ( stringBasedTypeKind ) => t . kind === stringBasedTypeKind ) ) { emitDeserializerCodeForStringObjects ( fieldName , t . kind , variableFieldName ) ; } else if ( t . kind === "string" ) { this . emitLine ( "value." , fieldName , " = " , variableFieldName , ";" ) ; } else if ( t . kind === "enum" ) { const { fieldType } = this . unionField ( u , t , true ) ; this . emitLine ( "value." , fieldName , " = " , fieldType , ".forValue(" , variableFieldName , ");" ) ; } else { this . emitLine ( "value." , fieldName , " = jsonParser.readValueAs(" , rendered , ".class);" ) ; } } ; const emitDeserializer = ( tokenTypes , kind ) : void => { const t = u . findMember ( kind ) ; if ( t === undefined ) return ; this . indent ( ( ) => { for ( const tokenType of tokenTypes ) { tokenCase ( tokenType ) ; } this . indent ( ( ) => { emitDeserializeType ( t ) ; this . emitLine ( "break;" ) ; } ) ; } ) ; } ; const emitStringDeserializer = ( ) => { const enumType = u . findMember ( "enum" ) ; const stringType = u . findMember ( "string" ) ; if ( stringBasedObjects . every ( ( kind ) => u . findMember ( kind ) === undefined ) && stringType === undefined && enumType === undefined ) return ; this . indent ( ( ) => { tokenCase ( "VALUE_STRING" ) ; this . indent ( ( ) => { const fromVariable = "string" ; this . emitLine ( "String " + fromVariable + " = jsonParser.readValueAs(String.class);" ) ; stringBasedObjects . forEach ( ( kind ) => { const type = u . findMember ( kind ) ; if ( type !== undefined ) { this . emitIgnoredTryCatchBlock ( ( ) => { emitDeserializeType ( type , fromVariable ) ; } ) ; } } ) ; if ( enumType !== undefined ) { this . emitIgnoredTryCatchBlock ( ( ) => { emitDeserializeType ( enumType , fromVariable ) ; } ) ; } if ( stringType !== undefined ) { emitDeserializeType ( stringType , fromVariable ) ; } this . emitLine ( "break;" ) ; } ) ; } ) ; } ; const emitNumberDeserializer = ( ) : void => { const integerType = u . findMember ( "integer" ) ; const doubleType = u . findMember ( "double" ) ; if ( doubleType === undefined && integerType === undefined ) return ; this . indent ( ( ) => { tokenCase ( "VALUE_NUMBER_INT" ) ; if ( integerType !== undefined ) { this . indent ( ( ) => { emitDeserializeType ( integerType ) ; this . emitLine ( "break;" ) ; } ) ; } if ( doubleType !== undefined ) { tokenCase ( "VALUE_NUMBER_FLOAT" ) ; this . indent ( ( ) => { emitDeserializeType ( doubleType ) ; this . emitLine ( "break;" ) ; } ) ; } } ) ; } ; const customObjectSerializer = [ "time" , "date" , "date-time" ] ; const serializerCodeForType = ( type , fieldName ) : Sourcelike => { switch ( type . kind ) { case "date" : return this . _dateTimeProvider . convertDateToString ( fieldName ) ; case "time" : return this . _dateTimeProvider . convertTimeToString ( fieldName ) ; case "date-time" : return this . _dateTimeProvider . convertDateTimeToString ( fieldName ) ; default : return panic ( "Requested type doesn't have custom serializer code!" ) ; } } ; const emitSerializeType = ( t ) : void => { let { fieldName } = this . unionField ( u , t , true ) ; this . emitBlock ( [ "if (obj." , fieldName , " != null)" ] , ( ) => { if ( customObjectSerializer . some ( ( customSerializerType ) => t . kind === customSerializerType ) ) { this . emitLine ( "jsonGenerator.writeObject(" , serializerCodeForType ( t , [ "obj." , fieldName ] ) , ");" ) ; } else { this . emitLine ( "jsonGenerator.writeObject(obj." , fieldName , ");" ) ; } this . emitLine ( "return;" ) ; } ) ; } ; const [ maybeNull , nonNulls ] = removeNullFromUnion ( u ) ; this . ensureBlankLine ( ) ; this . emitBlock ( [ "static class Deserializer extends JsonDeserializer<" , unionName , ">" ] , ( ) => { this . emitLine ( "@Override" ) ; this . emitBlock ( [ "public " , unionName , " deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException" , ] , ( ) => { this . emitLine ( unionName , " value = new " , unionName , "();" ) ; this . emitLine ( "switch (jsonParser.currentToken()) {" ) ; if ( maybeNull !== null ) emitNullDeserializer ( ) ; emitNumberDeserializer ( ) ; emitDeserializer ( [ "VALUE_TRUE" , "VALUE_FALSE" ] , "bool" ) ; emitStringDeserializer ( ) ; emitDeserializer ( [ "START_ARRAY" ] , "array" ) ; emitDeserializer ( [ "START_OBJECT" ] , "class" ) ; emitDeserializer ( [ "START_OBJECT" ] , "map" ) ; this . indent ( ( ) => this . emitLine ( 'default: throw new IOException("Cannot deserialize ' , unionName , '");' ) ) ; this . emitLine ( "}" ) ; this . emitLine ( "return value;" ) ; } ) ; } ) ; this . ensureBlankLine ( ) ; this . emitBlock ( [ "static class Serializer extends JsonSerializer<" , unionName , ">" ] , ( ) => { this . emitLine ( "@Override" ) ; this . emitBlock ( [ "public void serialize(" , unionName , " obj, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException" , ] , ( ) => { for ( const t of nonNulls ) { emitSerializeType ( t ) ; } if ( maybeNull !== null ) { this . emitLine ( "jsonGenerator.writeNull();" ) ; } else { this . emitLine ( 'throw new IOException("' , unionName , ' must not be null");' ) ; } } ) ; } ) ; } protected emitEnumSerializationAttributes ( _e ) { this . emitLine ( "@JsonValue" ) ; } protected emitEnumDeserializationAttributes ( _e ) { this . emitLine ( "@JsonCreator" ) ; } protected emitOffsetDateTimeConverterModule ( ) { this . emitLine ( "SimpleModule module = new SimpleModule();" ) ; if ( this . _dateTimeProvider . shouldEmitDateTimeConverter ) { this . emitLine ( "module.addDeserializer(" , this . _dateTimeProvider . dateTimeType , ".class, new JsonDeserializer<" , this . _dateTimeProvider . dateTimeType , ">() {" ) ; this . indent ( ( ) => { this . emitLine ( "@Override" ) ; this . emitBlock ( [ "public " , this . _dateTimeProvider . dateTimeType , " deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) " , "throws IOException, JsonProcessingException" ] , ( ) => { this . emitLine ( "String value = jsonParser.getText();" ) ; this . emitLine ( "return " , this . _dateTimeProvider . convertStringToDateTime ( "value" ) , ";" ) ; } ) ; } ) ; this . emitLine ( "});" ) ; } if ( ! this . _dateTimeProvider . shouldEmitTimeConverter ) { this . emitLine ( "module.addDeserializer(" , this . _dateTimeProvider . timeType , ".class, new JsonDeserializer<" , this . _dateTimeProvider . timeType , ">() {" ) ; this . indent ( ( ) => { this . emitLine ( "@Override" ) ; this . emitBlock ( [ "public " , this . _dateTimeProvider . timeType , " deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) " , "throws IOException, JsonProcessingException" ] , ( ) => { this . emitLine ( "String value = jsonParser.getText();" ) ; this . emitLine ( "return " , this . _dateTimeProvider . convertStringToTime ( "value" ) , ";" ) ; } ) ; } ) ; this . emitLine ( "});" ) ; } if ( ! this . _dateTimeProvider . shouldEmitDateConverter ) { this . emitLine ( "module.addDeserializer(" , this . _dateTimeProvider . dateType , ".class, new JsonDeserializer<" , this . _dateTimeProvider . dateType , ">() {" ) ; this . indent ( ( ) => { this . emitLine ( "@Override" ) ; this . emitBlock ( [ "public " , this . _dateTimeProvider . dateType , " deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) " , "throws IOException, JsonProcessingException" ] , ( ) => { this . emitLine ( "String value = jsonParser.getText();" ) ; this . emitLine ( "return " , this . _dateTimeProvider . convertStringToDate ( "value" ) , ";" ) ; } ) ; } ) ; this . emitLine ( "});" ) ; } this . emitLine ( "mapper.registerModule(module);" ) ; } protected emitConverterClass ( ) { this . startFile ( this . _converterClassname ) ; this . emitCommentLines ( [ "To use this code, add the following Maven dependency to your project:" , "" , this . _options . lombok ? "    org.projectlombok : lombok : 1.18.2" : "" , "    com.fasterxml.jackson.core     : jackson-databind          : 2.9.0" , this . _options . dateTimeProvider === "java8" ? "    com.fasterxml.jackson.datatype : jackson-datatype-jsr310   : 2.9.0" : "" , "" , "Import this package:" , "" , ] ) ; this . emitLine ( "//     import " , this . _options . packageName , ".Converter;" ) ; this . emitMultiline ( `//
// Then you can deserialize a JSON string with
//` ) ; this . forEachTopLevel ( "none" , ( t , name ) => { this . emitLine ( "//     " , this . javaType ( false , t ) , " data = Converter." , this . decoderName ( name ) , "(jsonString);" ) ; } ) ; this . ensureBlankLine ( ) ; const imports = [ "java.io.IOException" , "com.fasterxml.jackson.databind.*" , "com.fasterxml.jackson.databind.module.SimpleModule" , "com.fasterxml.jackson.core.JsonParser" , "com.fasterxml.jackson.core.JsonProcessingException" , "java.util.*" , ] . concat ( this . _dateTimeProvider . converterImports ) ; this . emitPackageAndImports ( imports ) ; this . ensureBlankLine ( ) ; this . emitBlock ( [ "public class Converter" ] , ( ) => { this . emitLine ( "// Date-time helpers" ) ; this . _dateTimeProvider . emitDateTimeConverters ( ) ; this . emitLine ( "// Serialize/deserialize helpers" ) ; this . forEachTopLevel ( "leading-and-interposing" , ( topLevelType , topLevelName ) => { const topLevelTypeRendered = this . javaType ( false , topLevelType ) ; this . emitBlock ( [ "public static " , topLevelTypeRendered , " " , this . decoderName ( topLevelName ) , "(String json) throws IOException" , ] , ( ) => { this . emitLine ( "return " , this . readerGetterName ( topLevelName ) , "().readValue(json);" ) ; } ) ; this . ensureBlankLine ( ) ; this . emitBlock ( [ "public static String " , this . encoderName ( topLevelName ) , "(" , topLevelTypeRendered , " obj) throws JsonProcessingException" , ] , ( ) => { this . emitLine ( "return " , this . writerGetterName ( topLevelName ) , "().writeValueAsString(obj);" ) ; } ) ; } ) ; this . forEachTopLevel ( "leading-and-interposing" , ( topLevelType , topLevelName ) => { const readerName = this . fieldOrMethodName ( "reader" , topLevelName ) ; const writerName = this . fieldOrMethodName ( "writer" , topLevelName ) ; this . emitLine ( "private static ObjectReader " , readerName , ";" ) ; this . emitLine ( "private static ObjectWriter " , writerName , ";" ) ; this . ensureBlankLine ( ) ; this . emitBlock ( [ "private static void " , this . methodName ( "instantiate" , "Mapper" , topLevelName ) , "()" ] , ( ) => { const renderedForClass = this . javaTypeWithoutGenerics ( false , topLevelType ) ; this . emitLine ( "ObjectMapper mapper = new ObjectMapper();" ) ; this . emitLine ( "mapper.findAndRegisterModules();" ) ; this . emitLine ( "mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);" ) ; this . emitOffsetDateTimeConverterModule ( ) ; this . emitLine ( readerName , " = mapper.readerFor(" , renderedForClass , ".class);" ) ; this . emitLine ( writerName , " = mapper.writerFor(" , renderedForClass , ".class);" ) ; } ) ; this . ensureBlankLine ( ) ; this . emitBlock ( [ "private static ObjectReader " , this . readerGetterName ( topLevelName ) , "()" ] , ( ) => { this . emitLine ( "if (" , readerName , " == null) " , this . methodName ( "instantiate" , "Mapper" , topLevelName ) , "();" ) ; this . emitLine ( "return " , readerName , ";" ) ; } ) ; this . ensureBlankLine ( ) ; this . emitBlock ( [ "private static ObjectWriter " , this . writerGetterName ( topLevelName ) , "()" ] , ( ) => { this . emitLine ( "if (" , writerName , " == null) " , this . methodName ( "instantiate" , "Mapper" , topLevelName ) , "();" ) ; this . emitLine ( "return " , writerName , ";" ) ; } ) ; } ) ; } ) ; this . finishFile ( ) ; } protected emitSourceStructure ( ) { this . emitConverterClass ( ) ; super . emitSourceStructure ( ) ; } }