Skip to content

Commit 665b2fd

Browse files
authored
Merge pull request #2 from leftshiftone/feature/GAIA-1599
Feature/gaia 1599
2 parents f7e4203 + 9dc9d75 commit 665b2fd

File tree

10 files changed

+554
-17
lines changed

10 files changed

+554
-17
lines changed

src/main/kotlin/implicit/Implicit.kt

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package implicit
22

33
import implicit.conversion.TypeConversion
44
import implicit.decorator.*
5+
import implicit.exception.ImplicitException
6+
import implicit.exception.ImplicitValidationException
7+
import implicit.exception.ImplicitViolations
58
import net.bytebuddy.ByteBuddy
69
import net.bytebuddy.NamingStrategy
710
import net.bytebuddy.description.method.MethodDescription
@@ -10,6 +13,8 @@ import net.bytebuddy.dynamic.DynamicType
1013
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy.Default.INJECTION
1114
import net.bytebuddy.implementation.MethodDelegation
1215
import net.bytebuddy.matcher.ElementMatchers.isDeclaredBy
16+
import java.lang.reflect.InvocationTargetException
17+
import java.lang.reflect.Method
1318
import java.util.concurrent.ConcurrentHashMap
1419
import java.util.function.Function
1520
import java.util.function.Supplier
@@ -87,24 +92,68 @@ class Implicit(val namingStrategy: (TypeDescription) -> CharSequence) {
8792
@Suppress("UNCHECKED_CAST")
8893
fun <T> getFunction(type: Class<T>, cache: Boolean = false): Function<Map<*, *>, out T> {
8994
return Function { map ->
90-
val instance:T = instantiate(type, cache)
91-
getType(instance!!).declaredMethods
92-
.filter { it.name.startsWith("set") }
93-
.forEach {
94-
val field = it.name.substring(3).decapitalize()
95-
if (map.containsKey(field)) {
96-
val clazz = it.parameterTypes[0]
97-
if (clazz.isInterface && clazz != Map::class.java && map[field] is Map<*, *>) {
98-
it.invoke(instance, instantiate(clazz, map[field] as Map<*, *>))
99-
}
100-
else
101-
it.invoke(instance, TypeConversion.convert(map[field], it.parameterTypes[0]))
95+
val instance: T = instantiate(type, cache)
96+
val implicitViolations = getType(instance!!).declaredMethods
97+
.filter { method -> isSetter(method) }
98+
.fold(ImplicitViolations(listOf())) { acc, entry ->
99+
try {
100+
val field = getFieldNameFromSetterMethod(entry)
101+
setMapValueInInstance(instance, entry, map[field])
102+
acc
103+
} catch (ex: ImplicitValidationException) {
104+
ImplicitViolations(acc.violations.plus(ex))
102105
}
103106
}
107+
if(!implicitViolations.violations.isEmpty()){
108+
throw implicitViolations
109+
}
104110
return@Function instance
105111
}
106112
}
107113

114+
fun <T> setMapValueInInstance(instance: T, method: Method, fieldValue: Any?) {
115+
if (fieldValue != null) {
116+
val clazz = method.parameterTypes[0]
117+
if (isNestedImplicitObject(clazz, fieldValue))
118+
invoke(instance, method, instantiateNestedObject(clazz, fieldValue as Map<*, *>))
119+
else
120+
invoke(instance, method, TypeConversion.convert(fieldValue, method.parameterTypes[0]))
121+
} else {
122+
initializeField(instance, method, method.parameterTypes[0])
123+
}
124+
}
125+
126+
fun getFieldNameFromSetterMethod(setter: Method): String = setter.name.substring(3).decapitalize()
127+
fun isNestedImplicitObject(objClass: Class<*>, fieldValue: Any?): Boolean = objClass.isInterface && objClass != Map::class.java && fieldValue is Map<*, *>
128+
fun isSetter(method: Method): Boolean = method.name.startsWith("set")
129+
fun isGetter(method: Method): Boolean = method.name.startsWith("get") || method.name.startsWith("is")
130+
fun <T> instantiateNestedObject(clazz: Class<T>, map: Map<*, *>) = instantiate(clazz, map)
131+
132+
fun <T> initializeField(instance: T, setter: Method, parameterType: Class<*>) {
133+
if (!isFieldInitialized(instance, getFieldNameFromSetterMethod(setter)) && !parameterType.isPrimitive) {
134+
invoke(instance, setter, null)
135+
}
136+
}
137+
138+
private fun <T> isFieldInitialized(instance: T, fieldName: String): Boolean {
139+
val fieldGetter = getType(instance!!).declaredMethods
140+
.filter { method -> isGetter(method) && method.name.contains(fieldName.capitalize()) }
141+
.first()
142+
143+
return fieldGetter.invoke(instance) != null
144+
}
145+
146+
fun <T> invoke(instance: T, setter: Method, value: Any?) {
147+
try {
148+
setter.invoke(instance, value)
149+
} catch (ex: InvocationTargetException) {
150+
when (ex.targetException) {
151+
is ImplicitException -> throw ex.targetException
152+
else -> throw ex
153+
}
154+
}
155+
}
156+
108157
@JvmOverloads
109158
@Suppress("UNCHECKED_CAST")
110159
fun <T> instantiate(type: Class<T>, cache: Boolean = false): T {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package implicit.annotation.validation
2+
3+
import implicit.annotation.Implicit
4+
import implicit.annotation.Implicit.Type.VALIDATOR
5+
import kotlin.annotation.AnnotationRetention.RUNTIME
6+
import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER
7+
8+
@Retention(RUNTIME)
9+
@Target(VALUE_PARAMETER)
10+
@Implicit(VALIDATOR)
11+
annotation class NotEmpty(val message:String = "")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package implicit.exception
2+
3+
class ImplicitViolations(val violations: List<ImplicitValidationException>) : ImplicitException("Implicit validations were detected: " + violations.fold("") { acc, entry -> acc.plus("[" + entry.message+ "]") })

src/main/kotlin/implicit/interceptor/generator/ValidationInterceptor.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ object ValidationInterceptor {
3939
is LowerThan -> return LowerThanValidator(annotation)
4040
is LowerEquals -> return LowerEqualsValidator(annotation)
4141
is NotBlank -> return NotBlankValidator(annotation)
42+
is NotEmpty -> return NotEmptyValidator(annotation)
4243
is MaxLength -> return MaxLengthValidator(annotation)
4344
is MinLength -> return MinLengthValidator(annotation)
4445
is ContentNotNull -> return ContentNullValidator(annotation)

src/main/kotlin/implicit/interceptor/validator/NotBlankValidator.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,20 @@ internal class NotBlankValidator(val annotation: NotBlank) : AbstractValidator()
88

99
override fun validate(values: List<*>, method: Method) {
1010
for (value in values) {
11-
if (value != null && value.toString().isBlank()) {
12-
if (annotation.message.isNotBlank())
13-
throw ImplicitValidationException(annotation.message)
14-
throw ImplicitValidationException("value of field '${method.name}' is blank")
11+
if (value ==null){
12+
throwImplicitValidationException(method)
13+
}
14+
when (value) {
15+
is String -> if(value.isBlank()) throwImplicitValidationException(method)
16+
1517
}
1618
}
1719
}
1820

21+
fun throwImplicitValidationException(method: Method){
22+
if (annotation.message.isNotBlank())
23+
throw ImplicitValidationException(annotation.message)
24+
throw ImplicitValidationException("value of field '${method.name}' is empty")
25+
}
26+
1927
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package implicit.interceptor.validator
2+
3+
import implicit.annotation.validation.NotEmpty
4+
import implicit.exception.ImplicitValidationException
5+
import java.lang.reflect.Method
6+
7+
internal class NotEmptyValidator(val annotation: NotEmpty) : AbstractValidator() {
8+
9+
override fun validate(values: List<*>, method: Method) {
10+
for (value in values) {
11+
if (value ==null){
12+
throwImplicitValidationException(method)
13+
}
14+
when (value) {
15+
is Collection<*> -> if(value.isEmpty()) throwImplicitValidationException(method)
16+
is String -> if(value.isEmpty()) throwImplicitValidationException(method)
17+
18+
}
19+
}
20+
}
21+
22+
fun throwImplicitValidationException(method: Method){
23+
if (annotation.message.isNotBlank())
24+
throw ImplicitValidationException(annotation.message)
25+
throw ImplicitValidationException("value of field '${method.name}' is empty")
26+
}
27+
28+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package implicit.validation.validator
2+
3+
import implicit.Implicit
4+
import implicit.annotation.validation.NotBlank
5+
import implicit.annotation.validation.NotEmpty
6+
import implicit.annotation.validation.NotNull
7+
import implicit.exception.ImplicitException
8+
import implicit.exception.ImplicitViolations
9+
import org.assertj.core.api.Assertions.assertThat
10+
import org.junit.jupiter.api.Assertions
11+
import org.junit.jupiter.api.Test
12+
import org.assertj.core.api.Assertions.assertThat
13+
14+
class ImplicitViolationsTest {
15+
16+
@Test
17+
fun happyPath() {
18+
val factory = Implicit { "${this.javaClass.name.toLowerCase()}.${it.simpleName}" }
19+
val map= mapOf(
20+
"notBlankName" to "name1",
21+
"notNullName" to "name2",
22+
"notEmptySet" to setOf("value")
23+
)
24+
25+
val instance = factory.instantiate(ITest::class.java, map)
26+
27+
Assertions.assertTrue(instance.getNotBlankName()!!.isNotBlank())
28+
Assertions.assertTrue(instance.getNotNullName()!=null)
29+
Assertions.assertTrue(instance.getNotEmptySet()!!.size>0)
30+
}
31+
32+
@Test
33+
fun oneViolationDetected() {
34+
val factory = Implicit { "${this.javaClass.name.toLowerCase()}.${it.simpleName}" }
35+
val map= mapOf(
36+
"notBlankName" to " ",
37+
"notNullName" to "name2",
38+
"notEmptySet" to setOf("value")
39+
)
40+
try{
41+
factory.instantiate(ITest::class.java, map)
42+
}catch (ex: ImplicitException ) {
43+
when (ex) {
44+
is ImplicitViolations -> {
45+
assertThat(ex.violations).hasSize(1)
46+
assertThat(ex.message).isEqualTo("Implicit validations were detected: [value of field 'setNotBlankName' is empty]")
47+
}
48+
else -> Assertions.fail("Implicit violation is expected")
49+
}
50+
}
51+
}
52+
53+
@Test
54+
fun multipleViolationsWhereDetected() {
55+
val factory = Implicit { "${this.javaClass.name.toLowerCase()}.${it.simpleName}" }
56+
val map= mapOf(
57+
"notBlankName" to " ",
58+
"notEmptySet" to setOf<String>()
59+
)
60+
try{
61+
factory.instantiate(ITest::class.java, map)
62+
}catch (ex: ImplicitException ) {
63+
when (ex) {
64+
is ImplicitViolations -> {
65+
assertThat(ex.violations).hasSize(3)
66+
}
67+
else -> Assertions.fail("Implicit violation is expected")
68+
}
69+
}
70+
}
71+
72+
73+
74+
75+
interface ITest {
76+
fun setNotNullName(@NotNull notNullName: String?)
77+
fun getNotNullName(): String?
78+
79+
fun setNotBlankName(@NotBlank notBlankName: String?)
80+
fun getNotBlankName(): String?
81+
82+
fun setNotEmptySet(@NotEmpty notEmptySet: Set<Any>?)
83+
fun getNotEmptySet(): Set<Any>?
84+
85+
}
86+
87+
}

0 commit comments

Comments
 (0)