Skip to content

Commit a6b49b1

Browse files
authored
Merge pull request #12 from leftshiftone/feature/GAIA-3355_Add_Trim_annotation_for_Strings
Feature/gaia 3355 add trim annotation for strings
2 parents 7e2a34a + 1b3ba09 commit a6b49b1

File tree

5 files changed

+196
-17
lines changed

5 files changed

+196
-17
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,15 +175,31 @@ fun instantiateEntityA(){
175175
))
176176
}
177177
````
178+
179+
## Trim
180+
181+
The @Trim annotation can be used on methods that return a String value and is used to trim the content of the field
182+
183+
````
184+
interface EntityA {
185+
@Trim
186+
fun getStringVal():String?
187+
}
188+
189+
````
190+
178191
## Development
179192

180193
### Release
194+
181195
Releases are triggered locally. Just a tag will be pushed and CI pipelines take care of the rest.
182196

183197
#### Major
198+
184199
Run `./gradlew final -x sendReleaseEmail -Prelease.scope=major` locally.
185200

186201
#### Minor
202+
187203
Run `./gradlew final -x sendReleaseEmail -Prelease.scope=minor` locally.
188204

189205
#### Patch
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package implicit.annotation.generator
2+
3+
import implicit.annotation.Implicit
4+
import implicit.annotation.Implicit.Type.GENERATOR
5+
import kotlin.annotation.AnnotationRetention.RUNTIME
6+
import kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS
7+
import kotlin.annotation.AnnotationTarget.FUNCTION
8+
9+
@Retention(RUNTIME)
10+
@Target(FUNCTION, ANNOTATION_CLASS)
11+
@Implicit(GENERATOR)
12+
annotation class Trim

src/main/kotlin/implicit/decorator/AddGetterSetterDecorator.kt

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package implicit.decorator
33
import implicit.annotation.Explicit
44
import implicit.annotation.Implicit
55
import implicit.annotation.generator.Default
6+
import implicit.annotation.generator.Trim
67
import implicit.interceptor.generator.DefaultInterceptor
8+
import implicit.interceptor.generator.TrimInterceptor
79
import implicit.interceptor.generator.ValidationInterceptor
810
import net.bytebuddy.description.ByteCodeElement
911
import net.bytebuddy.description.method.MethodDescription
@@ -46,23 +48,36 @@ class AddGetterSetterDecorator<T>(private val intf: Class<*>) : Function<Dynamic
4648
}
4749

4850
return builder
49-
.method(isDeclaredBy<ByteCodeElement>(typeMatcher)
50-
.and(not<MethodDescription>(isDefaultMethod<MethodDescription>()))
51-
.and(not<MethodDescription>(isAnnotatedWith(Explicit::class.java)))
52-
.and(not<MethodDescription>(isAnnotatedWith(Default::class.java)))
53-
.and(nameMatcher))
54-
.intercept(FieldAccessor.ofBeanProperty())
55-
.method(isDeclaredBy<ByteCodeElement>(typeMatcher)
56-
.and(annotationMatcher)
57-
.and(not<MethodDescription>(isAnnotatedWith(Default::class.java)))
58-
.and(nameMatcher))
59-
.intercept(MethodDelegation.to(ValidationInterceptor)
60-
.andThen(FieldAccessor.ofBeanProperty()))
61-
.method( isDeclaredBy<ByteCodeElement>(typeMatcher)
62-
.and(nameMatcher)
63-
.and(isAnnotatedWith(Default::class.java))
64-
)
65-
.intercept(MethodDelegation.to(DefaultInterceptor))
51+
.method(
52+
isDeclaredBy<ByteCodeElement>(typeMatcher)
53+
.and(not<MethodDescription>(isDefaultMethod<MethodDescription>()))
54+
.and(not<MethodDescription>(isAnnotatedWith(Explicit::class.java)))
55+
.and(not<MethodDescription>(isAnnotatedWith(Default::class.java)))
56+
.and(nameMatcher)
57+
)
58+
.intercept(FieldAccessor.ofBeanProperty())
59+
.method(
60+
isDeclaredBy<ByteCodeElement>(typeMatcher)
61+
.and(annotationMatcher)
62+
.and(not<MethodDescription>(isAnnotatedWith(Default::class.java)))
63+
.and(nameMatcher)
64+
)
65+
.intercept(
66+
MethodDelegation.to(ValidationInterceptor)
67+
.andThen(FieldAccessor.ofBeanProperty())
68+
)
69+
.method(
70+
isDeclaredBy<ByteCodeElement>(typeMatcher)
71+
.and(nameMatcher)
72+
.and(isAnnotatedWith(Default::class.java))
73+
)
74+
.intercept(MethodDelegation.to(DefaultInterceptor))
75+
.method(
76+
isDeclaredBy<ByteCodeElement>(typeMatcher)
77+
.and(nameMatcher)
78+
.and(isAnnotatedWith(Trim::class.java))
79+
)
80+
.intercept(MethodDelegation.to(TrimInterceptor))
6681
}
6782

6883
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package implicit.interceptor.generator
2+
3+
import net.bytebuddy.implementation.bind.annotation.Origin
4+
import net.bytebuddy.implementation.bind.annotation.RuntimeType
5+
import net.bytebuddy.implementation.bind.annotation.This
6+
import java.lang.reflect.Field
7+
import java.lang.reflect.Method
8+
import java.util.concurrent.ConcurrentHashMap
9+
10+
11+
object TrimInterceptor {
12+
13+
private val fieldCache = ConcurrentHashMap<String, Field>()
14+
15+
@RuntimeType
16+
fun intercept(@Origin method: Method, @This obj: Any): Any? {
17+
fieldCache.computeIfAbsent(getKey(method)) { _ -> getField(method, obj) }
18+
val fieldValue = fieldCache[getKey(method)]!!.get(obj)
19+
return trim(fieldValue)
20+
}
21+
22+
23+
private fun getField(method: Method, obj: Any): Field {
24+
val fieldName = method.name.substring(3).decapitalize()
25+
val field = obj::class.java.getDeclaredField(fieldName)
26+
field.isAccessible = true
27+
return field
28+
}
29+
30+
private fun trim(value: Any?): Any? {
31+
return if (value == null) null else when {
32+
value is String -> value.trim()
33+
else -> value
34+
}
35+
}
36+
37+
private fun getKey(method: Method): String {
38+
return method.declaringClass.name + "#" + method.name
39+
}
40+
41+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package implicit.generator
2+
3+
import implicit.Implicit
4+
import implicit.annotation.generator.Default
5+
import implicit.annotation.generator.Trim
6+
import implicit.annotation.validation.NotNull
7+
import org.junit.jupiter.api.Assertions
8+
import org.junit.jupiter.api.Test
9+
10+
class TrimTest {
11+
12+
@Test
13+
fun `Set a value with empty spaces in front and after the string`() {
14+
val factory = Implicit { "implicit.generator.trim_.${it.simpleName}" }
15+
val supplier = factory.getSupplier(Entity::class.java, true)
16+
17+
val pojo = supplier.get()
18+
pojo.setAbc("test")
19+
Assertions.assertEquals("test", pojo.getAbc())
20+
21+
pojo.setAbc(" test with space to trim ")
22+
Assertions.assertEquals("test with space to trim", pojo.getAbc())
23+
}
24+
25+
26+
@Test
27+
fun `Test String fields with and without Trim annotations`() {
28+
val factory = Implicit { "implicit.generator.trim_.${it.simpleName}" }
29+
val supplier = factory.getSupplier(Entity2::class.java)
30+
31+
val pojo = supplier.get()
32+
pojo.setStringValWithoutTrimAnnotation(" This is a value with multiple empty characters at the beginning and at the end of the value ")
33+
pojo.setStringValWithTrimAnnotation("\t\n This is a value with multiple empty characters at the beginning and at the end of the value. But it will be trimmed \r\n\n ")
34+
pojo.setLongVal(1L)
35+
36+
37+
Assertions.assertEquals(
38+
" This is a value with multiple empty characters at the beginning and at the end of the value ",
39+
pojo.getStringValWithoutTrimAnnotation()
40+
)
41+
Assertions.assertEquals(
42+
"This is a value with multiple empty characters at the beginning and at the end of the value. But it will be trimmed",
43+
pojo.getStringValWithTrimAnnotation()
44+
)
45+
Assertions.assertNull(pojo.getStringNullValWithTrimAnnotation())
46+
Assertions.assertEquals(0, pojo.getIntVal())
47+
Assertions.assertEquals(1.5f, pojo.getFloatVal())
48+
Assertions.assertEquals(1.5, pojo.getDoubleVal())
49+
Assertions.assertEquals(true, pojo.getBooleanVal())
50+
Assertions.assertEquals(0, pojo.getShortVal())
51+
Assertions.assertEquals(1, pojo.getLongVal())
52+
}
53+
54+
interface Entity {
55+
@Trim
56+
fun getAbc(): String
57+
58+
fun setAbc(@NotNull id: String)
59+
}
60+
61+
interface Entity2 {
62+
fun getStringValWithoutTrimAnnotation(): String?
63+
fun setStringValWithoutTrimAnnotation(@NotNull string: String)
64+
65+
66+
@Trim
67+
fun getStringValWithTrimAnnotation(): String?
68+
fun setStringValWithTrimAnnotation(@NotNull string: String)
69+
70+
@Trim
71+
fun getStringNullValWithTrimAnnotation(): String?
72+
73+
@Default("0")
74+
fun getIntVal(): Int?
75+
76+
@Default("1.5")
77+
fun getFloatVal(): Float?
78+
79+
@Default("1.5")
80+
fun getDoubleVal(): Double?
81+
82+
@Default("true")
83+
fun getBooleanVal(): Boolean?
84+
85+
@Default("0")
86+
fun getShortVal(): Short?
87+
88+
@Trim
89+
fun getLongVal(): Long?
90+
fun setLongVal(longValue: Long)
91+
92+
93+
}
94+
95+
}

0 commit comments

Comments
 (0)