Skip to content

Commit 9bead6e

Browse files
committed
[GAIA-1611] Extend Implicit to be able to instantiate nested objects which are in a collection.
1 parent e54c3a7 commit 9bead6e

File tree

3 files changed

+146
-3
lines changed

3 files changed

+146
-3
lines changed

src/main/kotlin/implicit/Implicit.kt

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package implicit
22

3+
import implicit.annotation.generator.GenericType
34
import implicit.conversion.TypeConversion
45
import implicit.decorator.*
56
import implicit.exception.ImplicitException
67
import implicit.exception.ImplicitValidationException
78
import implicit.exception.ImplicitViolations
9+
import implicit.extension.findAnnotation
810
import net.bytebuddy.ByteBuddy
911
import net.bytebuddy.NamingStrategy
1012
import net.bytebuddy.description.method.MethodDescription
@@ -18,6 +20,10 @@ import java.lang.reflect.Method
1820
import java.util.concurrent.ConcurrentHashMap
1921
import java.util.function.Function
2022
import java.util.function.Supplier
23+
import java.util.stream.Collectors
24+
import kotlin.reflect.KClass
25+
import kotlin.streams.toList
26+
2127

2228
class Implicit(val namingStrategy: (TypeDescription) -> CharSequence) {
2329

@@ -95,10 +101,12 @@ class Implicit(val namingStrategy: (TypeDescription) -> CharSequence) {
95101
val instance: T = instantiate(type, cache)
96102
val implicitViolations = getType(instance!!).declaredMethods
97103
.filter { method -> isSetter(method) }
98-
.fold(ImplicitViolations(listOf())) { acc, entry ->
104+
.fold(ImplicitViolations(listOf())) { acc, method ->
99105
try {
100-
val field = getFieldNameFromSetterMethod(entry)
101-
setMapValueInInstance(instance, entry, map[field])
106+
107+
val field = getFieldNameFromSetterMethod(method)
108+
val fieldValue = handleGenerics(type, method, map[field])
109+
setMapValueInInstance(instance, method, fieldValue)
102110
acc
103111
} catch (ex: ImplicitValidationException) {
104112
ImplicitViolations(acc.violations.plus(ex))
@@ -123,6 +131,42 @@ class Implicit(val namingStrategy: (TypeDescription) -> CharSequence) {
123131
}
124132
}
125133

134+
private fun <T> handleGenerics(type: Class<T>, method: Method, value: Any?): Any? {
135+
val valuesInACollection = convertToCollection(value)
136+
if (valuesInACollection!=null) {
137+
val genericType = getGenericType(type, method)
138+
if(genericType!=null){
139+
if (method.parameterTypes.first().isAssignableFrom(List::class.java)) {
140+
return instantiateGenericNestedObject(valuesInACollection, genericType).toList()
141+
}
142+
if (method.parameterTypes.first().isAssignableFrom(Set::class.java)) {
143+
return instantiateGenericNestedObject(valuesInACollection, genericType).collect(Collectors.toSet())
144+
}
145+
}
146+
}
147+
return value
148+
}
149+
150+
private fun convertToCollection(value: Any?) : Collection<*>?{
151+
if(value!=null){
152+
if (value is Collection<*>)
153+
return value
154+
else if (value is Array<*>)
155+
return value.toList()
156+
}
157+
return null
158+
}
159+
160+
private fun instantiateGenericNestedObject(value : Collection<*>, clazz: KClass<*>) = value.stream().map { instantiateNestedObject(clazz.java, it as Map<*, *>) }
161+
162+
private fun <T> getGenericType(type: Class<T>, method: Method): KClass<*>? {
163+
return type.declaredMethods
164+
.filter { it.name == method.name }
165+
.filter { it.isAnnotationPresent(GenericType::class.java) }
166+
.map { it.findAnnotation(GenericType::class)!!.value }
167+
.firstOrNull()
168+
}
169+
126170
fun getFieldNameFromSetterMethod(setter: Method): String = setter.name.substring(3).decapitalize()
127171
fun isNestedImplicitObject(objClass: Class<*>, fieldValue: Any?): Boolean = objClass.isInterface && objClass != Map::class.java && fieldValue is Map<*, *>
128172
fun isSetter(method: Method): Boolean = method.name.startsWith("set")
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package implicit.annotation.generator
2+
3+
import kotlin.annotation.AnnotationRetention.RUNTIME
4+
import kotlin.reflect.KClass
5+
6+
@Retention(RUNTIME)
7+
@Target(AnnotationTarget.FUNCTION)
8+
annotation class GenericType(val value: KClass<*>)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright (c) 2016-2019, Leftshift One
3+
* __________________
4+
* [2019] Leftshift One
5+
* All Rights Reserved.
6+
* NOTICE: All information contained herein is, and remains
7+
* the property of Leftshift One and its suppliers,
8+
* if any. The intellectual and technical concepts contained
9+
* herein are proprietary to Leftshift One
10+
* and its suppliers and may be covered by Patents,
11+
* patents in process, and are protected by trade secret or copyright law.
12+
* Dissemination of this information or reproduction of this material
13+
* is strictly forbidden unless prior written permission is obtained
14+
* from Leftshift One.
15+
*/
16+
17+
package implicit
18+
19+
import implicit.annotation.generator.GenericType
20+
import org.assertj.core.api.Assertions.assertThat
21+
import org.junit.jupiter.api.Test
22+
23+
class MapInitializationWithGenericsTest {
24+
25+
@Test
26+
fun `can convert types with list of nested types`() {
27+
val factory = Implicit { "${this.javaClass.name.toLowerCase()}.${it.simpleName}" }
28+
val function = factory.getFunction(IPojoAlpha::class.java)
29+
val instance = function.apply(mapOf(
30+
"partitionKey" to "abc",
31+
"contentList" to listOf(mapOf("partitionKey" to "123"), mapOf("partitionKey" to "456"))
32+
))
33+
34+
assertThat(instance.getContentList()).hasSize(2)
35+
assertThat(instance.getContentList()).extracting("partitionKey").contains("123","456")
36+
}
37+
38+
@Test
39+
fun `can convert types with set of nested types`() {
40+
val factory = Implicit { "${this.javaClass.name.toLowerCase()}.${it.simpleName}" }
41+
val function = factory.getFunction(IPojoAlpha::class.java)
42+
val instance = function.apply(mapOf(
43+
"partitionKey" to "abc",
44+
"contentSet" to setOf(mapOf("partitionKey" to "ABC"), mapOf("partitionKey" to "DEF"))
45+
))
46+
assertThat(instance.getContentSet()).hasSize(2)
47+
assertThat(instance.getContentSet()).extracting("partitionKey").contains("ABC","DEF")
48+
}
49+
50+
@Test
51+
fun `can convert types with array of nested types (SET)`() {
52+
val factory = Implicit { "${this.javaClass.name.toLowerCase()}.${it.simpleName}" }
53+
val function = factory.getFunction(IPojoAlpha::class.java)
54+
val instance = function.apply(mapOf(
55+
"partitionKey" to "abc",
56+
"contentSet" to arrayOf(mapOf("partitionKey" to "ABC"), mapOf("partitionKey" to "DEF"))
57+
))
58+
assertThat(instance.getContentSet()).hasSize(2)
59+
assertThat(instance.getContentSet()).extracting("partitionKey").contains("ABC","DEF")
60+
}
61+
62+
63+
64+
@Test
65+
fun `can convert types with array of nested types (LIST)`() {
66+
val factory = Implicit { "${this.javaClass.name.toLowerCase()}.${it.simpleName}" }
67+
val function = factory.getFunction(IPojoAlpha::class.java)
68+
val instance = function.apply(mapOf(
69+
"partitionKey" to "abc",
70+
"contentList" to arrayOf(mapOf("partitionKey" to "123"), mapOf("partitionKey" to "456"))
71+
))
72+
assertThat(instance.getContentList()).hasSize(2)
73+
assertThat(instance.getContentList()).extracting("partitionKey").contains("123","456")
74+
}
75+
76+
interface IPojoAlpha {
77+
fun getContentList(): List<IPojoBeta>
78+
@GenericType(IPojoBeta::class)
79+
fun setContentList(content: List<IPojoBeta>)
80+
81+
fun getContentSet(): Set<IPojoBeta>
82+
@GenericType(IPojoBeta::class)
83+
fun setContentSet(content: Set<IPojoBeta>)
84+
85+
}
86+
87+
interface IPojoBeta {
88+
fun getPartitionKey(): String
89+
fun setPartitionKey(str: String)
90+
}
91+
}

0 commit comments

Comments
 (0)