Skip to content

Commit

Permalink
Merge pull request #483 from Shalima/master
Browse files Browse the repository at this point in the history
IIEA-10827 Add support for class objects as inputs
  • Loading branch information
srinivasankavitha authored Nov 9, 2022
2 parents d127d01 + 0a5c412 commit 49c68bc
Show file tree
Hide file tree
Showing 8 changed files with 355 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ data class CodeGenConfig(
val generateInterfaceSetters: Boolean = true,
val includeImports: Map<String, String> = emptyMap(),
val includeEnumImports: Map<String, Map<String, String>> = emptyMap(),
val includeClassImports: Map<String, Map<String, String>> = emptyMap(),
val generateCustomAnnotations: Boolean = false,
var javaGenerateAllConstructor: Boolean = true,
val implementSerializable: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,10 @@ fun customAnnotation(annotationArgumentMap: MutableMap<String, Value<Value<*>>>,
val objectFields: List<ObjectField> = (annotationArgumentMap[ParserConstants.INPUTS] as ObjectValue).objectFields
for (objectField in objectFields) {
val codeBlock: CodeBlock = generateCode(
config,
objectField.value,
PackageParserUtil.getEnumPackage(config, (annotationArgumentMap[ParserConstants.NAME] as StringValue).value, objectField.name)
(annotationArgumentMap[ParserConstants.NAME] as StringValue).value,
objectField.name
)
annotation.addMember(objectField.name, codeBlock)
}
Expand All @@ -207,21 +209,31 @@ fun customAnnotation(annotationArgumentMap: MutableMap<String, Value<Value<*>>>,
/**
* Generates the code block containing the parameters of an annotation in the format value
*/
private fun generateCode(value: Value<Value<*>>, packageName: String = ""): CodeBlock =
private fun generateCode(config: CodeGenConfig, value: Value<Value<*>>, annotationName: String, prefix: String = ""): CodeBlock =
when (value) {
is BooleanValue -> CodeBlock.of("\$L", (value as BooleanValue).isValue)
is IntValue -> CodeBlock.of("\$L", (value as IntValue).value)
is StringValue -> CodeBlock.of("\$S", (value as StringValue).value)
is StringValue -> {
// If string value ends with .class and classImports mapping is provided, treat as Java Class
val string = (value as StringValue).value
if (string.endsWith(ParserConstants.CLASS_STRING)) {
val className = string.dropLast(ParserConstants.CLASS_LENGTH)
// Use annotationName and className in the PackagerParserUtil to get Class Package name.
val classPackage = PackageParserUtil.getClassPackage(config, annotationName, className)
if (classPackage.isNotEmpty()) CodeBlock.of("\$T.class", ClassName.get(classPackage, className))
else CodeBlock.of("\$S", string)
} else CodeBlock.of("\$S", string)
}
is FloatValue -> CodeBlock.of("\$L", (value as FloatValue).value)
// In an enum value the prefix/type (key in the parameters map for the enum) is used to get the package name from the config
// In an enum value the prefix (key in the parameters map for the enum) is used to get the package name from the config
// Limitation: Since it uses the enum key to lookup the package from the configs. 2 enums using different packages cannot have the same keys.
is EnumValue -> CodeBlock.of(
"\$T",
ClassName.get(packageName, (value as EnumValue).name)
ClassName.get(PackageParserUtil.getEnumPackage(config, annotationName, prefix), (value as EnumValue).name)
)
is ArrayValue ->
if ((value as ArrayValue).values.isEmpty()) CodeBlock.of("[]")
else CodeBlock.of("[\$L]", (value as ArrayValue).values.joinToString { v -> generateCode(value = v, if (v is EnumValue) packageName else "").toString() })
else CodeBlock.of("[\$L]", (value as ArrayValue).values.joinToString { v -> generateCode(config = config, value = v, annotationName = annotationName, prefix = if (v is EnumValue) prefix else "").toString() })
else -> CodeBlock.of("\$L", value)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,19 @@ private fun generateCode(config: CodeGenConfig, value: Value<Value<*>>, annotati
when (value) {
is BooleanValue -> CodeBlock.of("$prefix%L", (value as BooleanValue).isValue)
is IntValue -> CodeBlock.of("$prefix%L", (value as IntValue).value)
is StringValue -> CodeBlock.of("$prefix%S", (value as StringValue).value)
is StringValue -> {
// If string value ends with .class and classImports mapping is provided, treat as Kotlin KClass
val string = (value as StringValue).value
if (string.endsWith(ParserConstants.CLASS_STRING)) {
val className = string.dropLast(ParserConstants.CLASS_LENGTH)
// Use annotationName and className in the PackagerParserUtil to get Class Package name.
val classPackage = PackageParserUtil.getClassPackage(config, annotationName, className)
if (classPackage.isNotEmpty()) CodeBlock.of("$prefix%T::class", ClassName(classPackage, className))
else CodeBlock.of("$prefix%S", string)
} else CodeBlock.of("$prefix%S", string)
}
is FloatValue -> CodeBlock.of("$prefix%L", (value as FloatValue).value)
// In an enum value the prefix/type (key in the parameters map for the enum) is used to get the package name from the config
// Limitation: Since it uses the enum key to lookup the package from the configs. 2 enums using different packages cannot have the same keys.
is EnumValue -> CodeBlock.of(
"$prefix%M",
MemberName(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class PackageParserUtil {
* Retrieves the package value in the directive.
* If not present uses the default package in the config for that particular type of annotation.
* If neither of them are supplied the package name will be an empty String
* Also parses the simpleName/className from the name argument in the directive
* Also parses the simpleName/className from the name argument in the directive
*/
fun getAnnotationPackage(config: CodeGenConfig, name: String, type: String? = null): Pair<String, String> {
var packageName = name.substringBeforeLast(".", "")
Expand All @@ -48,5 +48,12 @@ class PackageParserUtil {
""
) ?: ""
}

fun getClassPackage(config: CodeGenConfig, annotationName: String, className: String): String {
return config.includeClassImports[annotationName]?.getOrDefault(
className,
""
) ?: ""
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ object ParserConstants {
const val REPLACE_WITH = "replaceWith"
const val REPLACE_WITH_CLASS = "ReplaceWith"
const val SITE_TARGET = "target"
const val CLASS_STRING = ".class"
const val CLASS_LENGTH = CLASS_STRING.length
}
Original file line number Diff line number Diff line change
Expand Up @@ -3361,6 +3361,157 @@ It takes a title and such.
assertThat(((fields[0].annotations[0] as AnnotationSpec).type as ClassName).canonicalName()).isEqualTo("com.test.anotherValidator.ValidName")
}

@Test
fun annotateOnTypesWithClassObjects() {
val schema = """
type Person @annotate(name: "ValidPerson", type: "validator", inputs: {groups: "BasicValidation.class"}) {
name: String @annotate(name: "com.test.anotherValidator.ValidName")
}
""".trimIndent()

val (dataTypes) = CodeGen(
CodeGenConfig(
schemas = setOf(schema),
packageName = basePackageName,
includeImports = mapOf(Pair("validator", "com.test.validator")),
includeClassImports = mapOf("ValidPerson" to mapOf(Pair("BasicValidation", "com.test.validator.groups"))),
generateCustomAnnotations = true
)
).generate()

assertThat(dataTypes.size).isEqualTo(1)
val person = dataTypes.single().typeSpec
assertThat(person.name).isEqualTo("Person")
assertThat(person.annotations).hasSize(1)
assertThat(((person.annotations[0] as AnnotationSpec).type as ClassName).simpleName()).isEqualTo("ValidPerson")
assertThat(((person.annotations[0] as AnnotationSpec).type as ClassName).canonicalName()).isEqualTo("com.test.validator.ValidPerson")
assertThat((person.annotations[0] as AnnotationSpec).members).hasSize(1)
assertThat((person.annotations[0] as AnnotationSpec).members["groups"]).isEqualTo(listOf(CodeBlock.of("\$L", "com.test.validator.groups.BasicValidation.class")))
val fields = person.fieldSpecs
assertThat(fields).hasSize(1)
assertThat(fields[0].annotations).hasSize(1)
assertThat(((fields[0].annotations[0] as AnnotationSpec).type as ClassName).simpleName()).isEqualTo("ValidName")
assertThat(((fields[0].annotations[0] as AnnotationSpec).type as ClassName).canonicalName()).isEqualTo("com.test.anotherValidator.ValidName")
}

@Test
fun annotateOnTypesWithClassObjectsNoMapping() {
val schema = """
type Person @annotate(name: "ValidPerson", type: "validator", inputs: {groups: "BasicValidation.class"}) {
name: String @annotate(name: "com.test.anotherValidator.ValidName")
}
""".trimIndent()

val (dataTypes) = CodeGen(
CodeGenConfig(
schemas = setOf(schema),
packageName = basePackageName,
includeImports = mapOf(Pair("validator", "com.test.validator")),
generateCustomAnnotations = true
)
).generate()

assertThat(dataTypes.size).isEqualTo(1)
val person = dataTypes.single().typeSpec
assertThat(person.name).isEqualTo("Person")
assertThat(person.annotations).hasSize(1)
assertThat(((person.annotations[0] as AnnotationSpec).type as ClassName).simpleName()).isEqualTo("ValidPerson")
assertThat(((person.annotations[0] as AnnotationSpec).type as ClassName).canonicalName()).isEqualTo("com.test.validator.ValidPerson")
assertThat((person.annotations[0] as AnnotationSpec).members).hasSize(1)
assertThat((person.annotations[0] as AnnotationSpec).members["groups"]).isEqualTo(listOf(CodeBlock.of("\$S", "BasicValidation.class"))) // treat as string when no mapping is provided
val fields = person.fieldSpecs
assertThat(fields).hasSize(1)
assertThat(fields[0].annotations).hasSize(1)
assertThat(((fields[0].annotations[0] as AnnotationSpec).type as ClassName).simpleName()).isEqualTo("ValidName")
assertThat(((fields[0].annotations[0] as AnnotationSpec).type as ClassName).canonicalName()).isEqualTo("com.test.anotherValidator.ValidName")
}

@Test
fun annotateOnTypesWithListOfClassObjects() {
val schema = """
type Person @annotate(name: "ValidPerson", type: "validator", inputs: {groups: ["BasicValidation.class","AdvanceValidation.class"]}) {
name: String @annotate(name: "com.test.anotherValidator.ValidName")
}
""".trimIndent()

val (dataTypes) = CodeGen(
CodeGenConfig(
schemas = setOf(schema),
packageName = basePackageName,
includeImports = mapOf(Pair("validator", "com.test.validator")),
includeClassImports = mapOf(
"ValidPerson" to mapOf(
Pair("BasicValidation", "com.test.validator.groups"),
Pair("AdvanceValidation", "com.test.validator.groups")
)
),
generateCustomAnnotations = true
)
).generate()

assertThat(dataTypes.size).isEqualTo(1)
val person = dataTypes.single().typeSpec
assertThat(person.name).isEqualTo("Person")
assertThat(person.annotations).hasSize(1)
assertThat(((person.annotations[0] as AnnotationSpec).type as ClassName).simpleName()).isEqualTo("ValidPerson")
assertThat(((person.annotations[0] as AnnotationSpec).type as ClassName).canonicalName()).isEqualTo("com.test.validator.ValidPerson")
assertThat((person.annotations[0] as AnnotationSpec).members).hasSize(1)
assertThat((person.annotations[0] as AnnotationSpec).members["groups"]).isEqualTo(listOf(CodeBlock.of("[\$L]", "com.test.validator.groups.BasicValidation.class, com.test.validator.groups.AdvanceValidation.class")))
val fields = person.fieldSpecs
assertThat(fields).hasSize(1)
assertThat(fields[0].annotations).hasSize(1)
assertThat(((fields[0].annotations[0] as AnnotationSpec).type as ClassName).simpleName()).isEqualTo("ValidName")
assertThat(((fields[0].annotations[0] as AnnotationSpec).type as ClassName).canonicalName()).isEqualTo("com.test.anotherValidator.ValidName")
}

@Test
fun annotateOnTypesWithMultipleListsOfClassObjects() {
val schema = """
type Person @annotate(name: "ValidPerson", type: "validator", inputs: {groups: ["BasicValidation.class","AdvanceValidation.class"]}) {
name: String @annotate(name: "com.test.anotherValidator.ValidName")
type: String @annotate(name: "ValidDateOfBirth", type: "dateOfBirth", inputs: {levels: ["PreliminaryValidation.class","SecondaryValidation.class"]})
}
""".trimIndent()

val (dataTypes) = CodeGen(
CodeGenConfig(
schemas = setOf(schema),
packageName = basePackageName,
includeImports = mapOf(Pair("validator", "com.test.validator"), Pair("dateOfBirth", "com.test.validator.dob")),
includeClassImports = mapOf(
"ValidPerson" to mapOf(
Pair("BasicValidation", "com.test.validator.groups"),
Pair("AdvanceValidation", "com.test.validator.groups")
),
"ValidDateOfBirth" to mapOf(
Pair("PreliminaryValidation", "com.test.validator.dob.levels"),
Pair("SecondaryValidation", "com.test.validator.dob.levels")
)
),
generateCustomAnnotations = true
)
).generate()

assertThat(dataTypes.size).isEqualTo(1)
val person = dataTypes.single().typeSpec
assertThat(person.name).isEqualTo("Person")
assertThat(person.annotations).hasSize(1)
assertThat(((person.annotations[0] as AnnotationSpec).type as ClassName).simpleName()).isEqualTo("ValidPerson")
assertThat(((person.annotations[0] as AnnotationSpec).type as ClassName).canonicalName()).isEqualTo("com.test.validator.ValidPerson")
assertThat((person.annotations[0] as AnnotationSpec).members).hasSize(1)
assertThat((person.annotations[0] as AnnotationSpec).members["groups"]).isEqualTo(listOf(CodeBlock.of("[\$L]", "com.test.validator.groups.BasicValidation.class, com.test.validator.groups.AdvanceValidation.class")))
val fields = person.fieldSpecs
assertThat(fields).hasSize(2)
assertThat(fields[0].annotations).hasSize(1)
assertThat(((fields[0].annotations[0] as AnnotationSpec).type as ClassName).simpleName()).isEqualTo("ValidName")
assertThat(((fields[0].annotations[0] as AnnotationSpec).type as ClassName).canonicalName()).isEqualTo("com.test.anotherValidator.ValidName")

assertThat(fields[1].annotations).hasSize(1)
assertThat(((fields[1].annotations[0] as AnnotationSpec).type as ClassName).simpleName()).isEqualTo("ValidDateOfBirth")
assertThat(((fields[1].annotations[0] as AnnotationSpec).type as ClassName).canonicalName()).isEqualTo("com.test.validator.dob.ValidDateOfBirth")
assertThat((fields[1].annotations[0] as AnnotationSpec).members["levels"]).isEqualTo(listOf(CodeBlock.of("[\$L]", "com.test.validator.dob.levels.PreliminaryValidation.class, com.test.validator.dob.levels.SecondaryValidation.class")))
}

@Test
fun annotateOnTypesWithEnums() {
val schema = """
Expand Down
Loading

0 comments on commit 49c68bc

Please sign in to comment.