Skip to content

Commit

Permalink
Add support for list component map (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
Idane committed Dec 10, 2020
1 parent e07867b commit cc741f7
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package com.antelopesystem.crudframework.utils.component.componentmap
import com.antelopesystem.crudframework.utils.cluster.lock.ParameterLock
import com.antelopesystem.crudframework.utils.component.componentmap.annotation.ComponentMap
import com.antelopesystem.crudframework.utils.component.componentmap.annotation.ComponentMapKey
import com.antelopesystem.crudframework.utils.component.componentmap.model.SingletonComponentMap
import com.antelopesystem.crudframework.utils.utils.ReflectionUtils
import com.antelopesystem.crudframework.utils.utils.getGenericClass
import com.antelopesystem.crudframework.utils.utils.resolveNestedGeneric
import org.apache.commons.lang3.ClassUtils
import org.springframework.aop.TargetClassAware
import org.springframework.aop.framework.Advised
Expand All @@ -14,7 +16,7 @@ import java.lang.reflect.Method

class ComponentMapPostProcessor : BeanPostProcessor {

private val componentMaps: MutableMap<Pair<Class<*>, Class<*>>, MutableMap<Any, Any>> = mutableMapOf()
private val componentMaps: MutableMap<ComponentMapIdentifier, MutableMap<Any, MutableList<Any>>> = mutableMapOf()

override fun postProcessAfterInitialization(bean: Any, beanName: String?): Any {
try {
Expand Down Expand Up @@ -44,31 +46,42 @@ class ComponentMapPostProcessor : BeanPostProcessor {
val mapped = field.getAnnotation(ComponentMap::class.java)
try {
val keyClazz = field.getGenericClass(0)!!
val valueClazz = field.getGenericClass(1)!!
var valueClazz = field.getGenericClass(1)!!
var isList = false
if(Collection::class.java.isAssignableFrom(valueClazz)) {
isList = true
valueClazz = field.resolveNestedGeneric(1)
}

ReflectionUtils.makeAccessible(field)
field[handler] = getOrCreateComponentMap(keyClazz, valueClazz)
val map = getOrCreateComponentMap(keyClazz, valueClazz)
if(isList) {
field[handler] = map
} else {
field[handler] = SingletonComponentMap(map)
}
} catch (e: java.lang.Exception) {
}
}
}
}

private fun getOrCreateComponentMap(initialKeyType: Class<*>, initialValueType: Class<*>) : MutableMap<Any, Any> {
val pair = initialKeyType to initialValueType
var map = componentMaps[pair]
private fun getOrCreateComponentMap(initialKeyType: Class<*>, initialValueType: Class<*>) : MutableMap<Any, MutableList<Any>> {
val identifier = ComponentMapIdentifier(initialKeyType, initialValueType)
var map = componentMaps[identifier]
if(map != null) {
return map
}
val lock = ParameterLock.getCanonicalParameterLock("${initialKeyType.canonicalName}_${initialValueType.canonicalName}")
lock.lock()
try {
map = componentMaps[pair]
map = componentMaps[identifier]
if(map != null) {
return map
}

componentMaps[pair] = mutableMapOf()
return componentMaps[pair]!!
componentMaps[identifier] = mutableMapOf()
return componentMaps[identifier]!!
} finally {
lock.unlock()
}
Expand All @@ -85,7 +98,8 @@ class ComponentMapPostProcessor : BeanPostProcessor {
val keyClass = key::class.java
val valueClass = getMethodDeclarer(method)
val map = getOrCreateComponentMap(keyClass, valueClass)
map[key] = bean
val list = map.computeIfAbsent(key) { mutableListOf() }
list += bean
} catch (e: Exception) {
}
}
Expand All @@ -95,6 +109,8 @@ class ComponentMapPostProcessor : BeanPostProcessor {

}

private data class ComponentMapIdentifier(val keyClazz: Class<*>, val valueClazz: Class<*>)

fun getMethodDeclarer(method: Method): Class<*> {
var declaringClass: Class<*> = method.declaringClass
val methodName: String = method.name
Expand All @@ -115,4 +131,5 @@ fun getMethodDeclarer(method: Method): Class<*> {
}

error("Could not find method declarer for ${method.declaringClass.canonicalName}::${method.name}")
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.antelopesystem.crudframework.utils.component.componentmap.model

class SingletonComponentMap<K, V>(private val originalMap: Map<K, Collection<V>>) : Map<K, V> {
override val size: Int
get() = originalMap.size

override val values: Collection<V>
get() = originalMap.values.map { it.first() }
override val entries: Set<Map.Entry<K, V>>
get() = originalMap.map {
it.key to it.value.first()
}.toMap().entries
override val keys: Set<K>
get() = originalMap.keys

override fun containsKey(key: K): Boolean {
return originalMap.containsKey(key)
}

override fun containsValue(value: V): Boolean {
return originalMap.values.any { it.first() == value }
}

override fun get(key: K): V? {
return originalMap[key]?.first()
}

override fun isEmpty(): Boolean {
return originalMap.isEmpty()
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,37 @@ fun Field.getGenericClass(index: Int) : Class<*>? {
val genericType = this.genericType as ParameterizedTypeImpl
val typeArgument = genericType.actualTypeArguments[index]
if(typeArgument is WildcardTypeImpl) {
return typeArgument.upperBounds[0] as Class<*>
val upperBound = typeArgument.upperBounds[0]
return if(upperBound is ParameterizedTypeImpl) {
upperBound.rawType
} else {
upperBound as Class<*>
}
}
return typeArgument as Class<*>
} catch(e: ArrayIndexOutOfBoundsException) { null }
}

/**
* Resolve a sublevel generic type (For example Map<*, List<Child>>)
*/
fun Field.resolveNestedGeneric(parentIndex: Int, childIndex: Int = 0): Class<*> {
val genericType = this.genericType
if(genericType !is ParameterizedTypeImpl) {
error("${this.type} is not a parameterized type")
}
var childType = genericType.actualTypeArguments[parentIndex]
while(childType is WildcardTypeImpl) {
childType = childType.upperBounds[0]
}

if(childType is ParameterizedTypeImpl) {
var returnValue = childType.actualTypeArguments[childIndex]
while(returnValue is WildcardTypeImpl) {
returnValue = returnValue.upperBounds[0]
}
return returnValue as Class<*>
}

return childType as Class<*>
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.antelopesystem.crudframework.utils.component.componentmap
import ComponentMapTestConfig
import TestImpl1
import TestImpl2
import TestImpl2Duplicate
import TestMapUser
import com.antelopesystem.crudframework.utils.component.componentmap.configuration.ComponentMapConfiguration
import org.junit.Test
Expand All @@ -23,6 +24,9 @@ class ComponentMapTest {
@Autowired
private lateinit var testImpl2: TestImpl2

@Autowired
private lateinit var testImpl2Duplicate: TestImpl2Duplicate

@Autowired
private lateinit var testMapUser: TestMapUser

Expand All @@ -38,4 +42,13 @@ class ComponentMapTest {
)
assertEquals(expected, testMapUser.map)
}

@Test
fun `test component map of list populates correctly`() {
val expected = mapOf(
testImpl1.type to listOf(testImpl1),
testImpl2.type to listOf(testImpl2, testImpl2Duplicate)
)
assertEquals(expected, testMapUser.multiMap)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import com.antelopesystem.crudframework.utils.component.componentmap.annotation.
import com.antelopesystem.crudframework.utils.component.componentmap.annotation.ComponentMapKey
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.annotation.Order

interface TestHandler {
@get:ComponentMapKey
Expand All @@ -18,18 +19,32 @@ class TestImpl2 : TestHandler {
get() = "Test2"
}

class TestImpl2Duplicate : TestHandler {
override val type: String
get() = "Test2"
}

class TestMapUser {
@ComponentMap
lateinit var map: Map<String, TestHandler>

@ComponentMap
lateinit var multiMap: Map<String, List<TestHandler>>
}

@Configuration
class ComponentMapTestConfig {
@Bean
fun exampleImpl1() = TestImpl1()
@Order(1)
fun testImpl1() = TestImpl1()

@Bean
@Order(2)
fun testImpl2() = TestImpl2()

@Bean
fun exampleImpl2() = TestImpl2()
@Order(3)
fun testImpl2Duplicate() = TestImpl2Duplicate()

@Bean
fun testMapUser() = TestMapUser()
Expand Down

0 comments on commit cc741f7

Please sign in to comment.