Skip to content

Commit

Permalink
Merge pull request #4 from MechDancer/generic
Browse files Browse the repository at this point in the history
Rework generic finding algorithm, making unique component and named component interfaces
  • Loading branch information
YdrMaster authored Mar 10, 2022
2 parents 847dd69 + 79893b7 commit 8f8c4e0
Show file tree
Hide file tree
Showing 11 changed files with 327 additions and 78 deletions.
39 changes: 28 additions & 11 deletions src/main/kotlin/org/mechdancer/dependency/Functions.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.mechdancer.dependency

import java.lang.reflect.ParameterizedType
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.full.starProjectedType
import kotlin.reflect.jvm.jvmErasure

/**
* Find a list dependencies with type [C]
Expand Down Expand Up @@ -60,14 +62,29 @@ inline fun scope(block: DynamicScope.() -> Unit) =
DynamicScope().apply(block)

/**
* Find first generic type that has an upper bound [upper]
* @receiver Type that has a generic type to be found
* Find generic type in super class hierarchies which has is subtype of [upper] recursively
* For example, we have the following two classes:
* ```kotlin
* abstract class Foo<T : Foo<T>>
* class Bar : Foo<Bar>()
*```
* And we can find type parameter `T` instantiated with `Bar`:
* ```kotlin
* val bar = Bar()
* // This returns KClass of Bar
* bar.javaClass.kotlin.findSuperGenericTypeRecursively(Foo::class))
* ```
*/
fun KClass<*>.firstGenericType(upper: KClass<*>) =
(java.genericSuperclass as? ParameterizedType)
?.actualTypeArguments
?.asSequence()
?.mapNotNull { it as? Class<*> }
?.find { t -> upper.java.isAssignableFrom(t) }
?.kotlin
?: throw RuntimeException("Unable to find component type.")
@Suppress("UNCHECKED_CAST")
fun <T : Any> KClass<*>.findSuperGenericTypeRecursively(upper: KClass<T>): KClass<out T> =
supertypes
.find { it.isSubtypeOf(upper.starProjectedType) }
?.arguments
?.firstNotNullOfOrNull {
it.type?.takeIf { type -> type.isSubtypeOf(upper.starProjectedType) }
}
?.jvmErasure as? KClass<out T>
?: supertypes.firstNotNullOfOrNull {
(it as? KClass<*>)?.findSuperGenericTypeRecursively(upper)
}
?: throw RuntimeException("Unable to find generic type.")
25 changes: 25 additions & 0 deletions src/main/kotlin/org/mechdancer/dependency/INamedComponent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.mechdancer.dependency

import kotlin.reflect.KClass
import kotlin.reflect.safeCast

/**
* [INamedComponent] is a type of [Component] associated with unique [name] and [T]
*
* [INamedComponent]s with the same type and name can not coexist in the scope
*/
interface INamedComponent<T : INamedComponent<T>> : Component {

val name: String

val type: KClass<out INamedComponent<*>>

fun defaultType() = javaClass.kotlin.findSuperGenericTypeRecursively(INamedComponent::class)

fun defaultEquals(other: Any?) =
this === other || type.safeCast(other)?.name == name

fun defaultHashCode() =
(type.hashCode() shl 31) + name.hashCode()

}
19 changes: 19 additions & 0 deletions src/main/kotlin/org/mechdancer/dependency/IUniqueComponent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.mechdancer.dependency

import kotlin.reflect.KClass
import kotlin.reflect.safeCast

/**
* [IUniqueComponent] is a type of [Component] associated with unique type [T]
*
* [IUniqueComponent]s with the same type can not coexist in the scope
*/
interface IUniqueComponent<T : IUniqueComponent<T>> : Component {
val type: KClass<out IUniqueComponent<*>>

fun defaultType() = javaClass.kotlin.findSuperGenericTypeRecursively(IUniqueComponent::class)

fun defaultHashCode() = type.hashCode()

fun defaultEquals(other: Any?) = this === other || type.safeCast(other) !== null
}
20 changes: 7 additions & 13 deletions src/main/kotlin/org/mechdancer/dependency/NamedComponent.kt
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
package org.mechdancer.dependency

import kotlin.reflect.KClass
import kotlin.reflect.safeCast

/**
* [NamedComponent] is a type of [Component] associated with unique [name] and [T]
*
* [NamedComponent]s with the same type and name can not coexist in the scope
* Class version of [INamedComponent] for convenience
*/
abstract class NamedComponent<T : NamedComponent<T>>
(val name: String, type: KClass<T>? = null) : Component {
abstract class NamedComponent<T : NamedComponent<T>>(override val name: String) :
INamedComponent<T> {
override fun equals(other: Any?) = defaultEquals(other)

@Suppress("UNCHECKED_CAST")
val type = type ?: javaClass.kotlin.firstGenericType(NamedComponent::class) as KClass<T>
override fun hashCode(): Int = defaultHashCode()

override fun equals(other: Any?) =
this === other || type.safeCast(other)?.name == name
override val type: KClass<out INamedComponent<*>> by lazy { defaultType() }

override fun hashCode() =
(type.hashCode() shl 31) + name.hashCode()
}
}
18 changes: 6 additions & 12 deletions src/main/kotlin/org/mechdancer/dependency/UniqueComponent.kt
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
package org.mechdancer.dependency

import kotlin.reflect.KClass
import kotlin.reflect.safeCast

/**
* [UniqueComponent] is a type of [Component] associated with unique type [T]
*
* [UniqueComponent]s with the same type can not coexist in the scope
* Class version of [IUniqueComponent] for convenience
*/
abstract class UniqueComponent<T : UniqueComponent<T>>(type: KClass<T>? = null) : Component {
abstract class UniqueComponent<T : UniqueComponent<T>> :
IUniqueComponent<T> {

val type = type ?: javaClass.kotlin.firstGenericType(UniqueComponent::class)
override val type by lazy { defaultType() }

override fun equals(other: Any?) =
this === other || type.safeCast(other) !== null
override fun equals(other: Any?) = defaultEquals(other)

override fun hashCode() =
type.hashCode()
override fun hashCode() = defaultHashCode()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.mechdancer.dependency

import kotlin.reflect.KClass
import kotlin.reflect.safeCast

class UniqueComponentWrapper<T : Any> @PublishedApi internal constructor(
val type: KClass<*>,
val wrapped: T
) : Component {

companion object {
inline operator fun <reified T : Any> invoke(wrapped: T) =
UniqueComponentWrapper(T::class, wrapped)
}

override fun equals(other: Any?): Boolean =
this === other || (other is UniqueComponentWrapper<*> && type.safeCast(other.wrapped) !== null)

override fun hashCode(): Int = type.hashCode()

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,32 @@ class AnnotatedInjector<T : Any>(private val dependent: T, type: KClass<T>) : Sc
javaField!!.annotations.firstNotNullOfOrNull { it as? Name }?.name

fun Component.toPredicate(name: String?) =
if (this is NamedComponent<*>)
if (this is INamedComponent<*>)
name == this.name
else true

allFields
.filter { it.javaField?.isAnnotationPresent(Must::class.java) ?: false }
.map {
val name = it.getName()
TypeSafeDependency.Dependency(it.returnType.jvmErasure as KClass<out Component>) { component ->
component.toPredicate(name)
val needsUnwrap = it.javaField!!.isAnnotationPresent(Unwrap::class.java)
TypeSafeDependency.Dependency(
if (needsUnwrap) UniqueComponentWrapper::class
else it.returnType.jvmErasure as KClass<out Component>
) { component ->
(if (needsUnwrap)
(component is UniqueComponentWrapper<*> && it.returnType.jvmErasure ==
component.type)
else
true) && component.toPredicate(name)
}.also { dep ->
dep.setOnSetListener { component ->
it.javaField!!.apply {
isAccessible = true
set(dependent, component)
if (needsUnwrap && component is UniqueComponentWrapper<*>)
set(dependent, component.wrapped)
else
set(dependent, component)
}
}
}
Expand All @@ -52,13 +63,24 @@ class AnnotatedInjector<T : Any>(private val dependent: T, type: KClass<T>) : Sc
if (!it.returnType.isMarkedNullable)
throw RuntimeException("$it was annotated with Maybe, but ut is not a nullable property")
val name = it.getName()
TypeSafeDependency.WeakDependency(it.returnType.jvmErasure as KClass<out Component>) { component ->
component.toPredicate(name)
val needsUnwrap = it.javaField!!.isAnnotationPresent(Unwrap::class.java)
TypeSafeDependency.WeakDependency(
if (needsUnwrap) UniqueComponentWrapper::class
else it.returnType.jvmErasure as KClass<out Component>
) { component ->
(if (needsUnwrap)
component is UniqueComponentWrapper<*> && it.returnType.jvmErasure ==
component.type
else
true) && component.toPredicate(name)
}.also { dep ->
dep.setOnSetListener { component ->
it.javaField!!.apply {
isAccessible = true
set(dependent, component)
if (needsUnwrap && component is UniqueComponentWrapper<*>)
set(dependent, component.wrapped)
else
set(dependent, component)
}
}
dep.setOnClearListener {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,13 @@ annotation class Maybe
@Target(AnnotationTarget.FIELD)
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class Name(val name: String)
annotation class Name(val name: String)

/**
* Specify that this is a wrapped unique dependency
* which needs to be unwrapped
*/
@Target(AnnotationTarget.FIELD)
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class Unwrap
Loading

0 comments on commit 8f8c4e0

Please sign in to comment.