diff --git a/src/main/kotlin/org/mechdancer/dependency/Functions.kt b/src/main/kotlin/org/mechdancer/dependency/Functions.kt index 407ce07..324b7a9 100644 --- a/src/main/kotlin/org/mechdancer/dependency/Functions.kt +++ b/src/main/kotlin/org/mechdancer/dependency/Functions.kt @@ -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] @@ -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> + * class Bar : Foo() + *``` + * 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 KClass<*>.findSuperGenericTypeRecursively(upper: KClass): KClass = + supertypes + .find { it.isSubtypeOf(upper.starProjectedType) } + ?.arguments + ?.firstNotNullOfOrNull { + it.type?.takeIf { type -> type.isSubtypeOf(upper.starProjectedType) } + } + ?.jvmErasure as? KClass + ?: supertypes.firstNotNullOfOrNull { + (it as? KClass<*>)?.findSuperGenericTypeRecursively(upper) + } + ?: throw RuntimeException("Unable to find generic type.") diff --git a/src/main/kotlin/org/mechdancer/dependency/INamedComponent.kt b/src/main/kotlin/org/mechdancer/dependency/INamedComponent.kt new file mode 100644 index 0000000..3386e18 --- /dev/null +++ b/src/main/kotlin/org/mechdancer/dependency/INamedComponent.kt @@ -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> : Component { + + val name: String + + val type: KClass> + + 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() + +} diff --git a/src/main/kotlin/org/mechdancer/dependency/IUniqueComponent.kt b/src/main/kotlin/org/mechdancer/dependency/IUniqueComponent.kt new file mode 100644 index 0000000..2513bce --- /dev/null +++ b/src/main/kotlin/org/mechdancer/dependency/IUniqueComponent.kt @@ -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> : Component { + val type: KClass> + + fun defaultType() = javaClass.kotlin.findSuperGenericTypeRecursively(IUniqueComponent::class) + + fun defaultHashCode() = type.hashCode() + + fun defaultEquals(other: Any?) = this === other || type.safeCast(other) !== null +} \ No newline at end of file diff --git a/src/main/kotlin/org/mechdancer/dependency/NamedComponent.kt b/src/main/kotlin/org/mechdancer/dependency/NamedComponent.kt index 2441739..71e18f6 100644 --- a/src/main/kotlin/org/mechdancer/dependency/NamedComponent.kt +++ b/src/main/kotlin/org/mechdancer/dependency/NamedComponent.kt @@ -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> -(val name: String, type: KClass? = null) : Component { +abstract class NamedComponent>(override val name: String) : + INamedComponent { + override fun equals(other: Any?) = defaultEquals(other) - @Suppress("UNCHECKED_CAST") - val type = type ?: javaClass.kotlin.firstGenericType(NamedComponent::class) as KClass + override fun hashCode(): Int = defaultHashCode() - override fun equals(other: Any?) = - this === other || type.safeCast(other)?.name == name + override val type: KClass> by lazy { defaultType() } - override fun hashCode() = - (type.hashCode() shl 31) + name.hashCode() -} +} \ No newline at end of file diff --git a/src/main/kotlin/org/mechdancer/dependency/UniqueComponent.kt b/src/main/kotlin/org/mechdancer/dependency/UniqueComponent.kt index 3880e97..b593622 100644 --- a/src/main/kotlin/org/mechdancer/dependency/UniqueComponent.kt +++ b/src/main/kotlin/org/mechdancer/dependency/UniqueComponent.kt @@ -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>(type: KClass? = null) : Component { +abstract class UniqueComponent> : + IUniqueComponent { - 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() } diff --git a/src/main/kotlin/org/mechdancer/dependency/UniqueComponentWrapper.kt b/src/main/kotlin/org/mechdancer/dependency/UniqueComponentWrapper.kt new file mode 100644 index 0000000..c1194e0 --- /dev/null +++ b/src/main/kotlin/org/mechdancer/dependency/UniqueComponentWrapper.kt @@ -0,0 +1,21 @@ +package org.mechdancer.dependency + +import kotlin.reflect.KClass +import kotlin.reflect.safeCast + +class UniqueComponentWrapper @PublishedApi internal constructor( + val type: KClass<*>, + val wrapped: T +) : Component { + + companion object { + inline operator fun 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() + +} \ No newline at end of file diff --git a/src/main/kotlin/org/mechdancer/dependency/annotated/AnnotatedInjector.kt b/src/main/kotlin/org/mechdancer/dependency/annotated/AnnotatedInjector.kt index 1e870b0..c63146f 100644 --- a/src/main/kotlin/org/mechdancer/dependency/annotated/AnnotatedInjector.kt +++ b/src/main/kotlin/org/mechdancer/dependency/annotated/AnnotatedInjector.kt @@ -22,7 +22,7 @@ class AnnotatedInjector(private val dependent: T, type: KClass) : 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 @@ -30,13 +30,24 @@ class AnnotatedInjector(private val dependent: T, type: KClass) : Sc .filter { it.javaField?.isAnnotationPresent(Must::class.java) ?: false } .map { val name = it.getName() - TypeSafeDependency.Dependency(it.returnType.jvmErasure as KClass) { component -> - component.toPredicate(name) + val needsUnwrap = it.javaField!!.isAnnotationPresent(Unwrap::class.java) + TypeSafeDependency.Dependency( + if (needsUnwrap) UniqueComponentWrapper::class + else it.returnType.jvmErasure as KClass + ) { 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) } } } @@ -52,13 +63,24 @@ class AnnotatedInjector(private val dependent: T, type: KClass) : 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) { component -> - component.toPredicate(name) + val needsUnwrap = it.javaField!!.isAnnotationPresent(Unwrap::class.java) + TypeSafeDependency.WeakDependency( + if (needsUnwrap) UniqueComponentWrapper::class + else it.returnType.jvmErasure as KClass + ) { 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 { diff --git a/src/main/kotlin/org/mechdancer/dependency/annotated/Annotations.kt b/src/main/kotlin/org/mechdancer/dependency/annotated/Annotations.kt index 6ac9d3c..ad9d0f4 100644 --- a/src/main/kotlin/org/mechdancer/dependency/annotated/Annotations.kt +++ b/src/main/kotlin/org/mechdancer/dependency/annotated/Annotations.kt @@ -23,4 +23,13 @@ annotation class Maybe @Target(AnnotationTarget.FIELD) @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) -annotation class Name(val name: String) \ No newline at end of file +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 \ No newline at end of file diff --git a/src/main/kotlin/org/mechdancer/dependency/manager/Functions.kt b/src/main/kotlin/org/mechdancer/dependency/manager/Functions.kt index da05ce2..2ab5d47 100644 --- a/src/main/kotlin/org/mechdancer/dependency/manager/Functions.kt +++ b/src/main/kotlin/org/mechdancer/dependency/manager/Functions.kt @@ -1,7 +1,8 @@ package org.mechdancer.dependency.manager -import org.mechdancer.dependency.NamedComponent -import org.mechdancer.dependency.UniqueComponent +import org.mechdancer.dependency.INamedComponent +import org.mechdancer.dependency.IUniqueComponent +import org.mechdancer.dependency.UniqueComponentWrapper import kotlin.reflect.KClass /** @@ -12,149 +13,172 @@ fun managedHandler() = object : ManagedHandler { } /** - * Declare a strict [UniqueComponent] dependency with type [C], + * Declare a strict [IUniqueComponent] dependency with type [C], * * @return the dependency declaration */ -fun > +fun > DependencyManager.dependOnStrict(type: KClass) = dependOnStrict(type) { true } /** - * Declare a strict [NamedComponent] dependency with [name] and type [C], + * Declare a strict [INamedComponent] dependency with [name] and type [C], * * @return the dependency declaration */ -fun > +fun > DependencyManager.dependOnStrict(type: KClass, name: String) = dependOnStrict(type) { it.name == name } /** - * Declare a weak [UniqueComponent] dependency with type [C], + * Declare a weak [IUniqueComponent] dependency with type [C], * * @return the dependency declaration */ -fun > +fun > DependencyManager.dependOnWeak(type: KClass) = dependOnWeak(type) { true } /** - * Declare a weak [NamedComponent] dependency with [name] and type [C], + * Declare a weak [INamedComponent] dependency with [name] and type [C], * * @return the dependency declaration */ -fun > +fun > DependencyManager.dependOnWeak(type: KClass, name: String) = dependOnWeak(type) { it.name == name } /** - * Declare a strict [UniqueComponent] dependency with type [C], + * Declare a strict [IUniqueComponent] dependency with type [C], * * @return the dependency declaration */ -inline fun > +inline fun > DependencyManager.dependency() = dependOnStrict(C::class) { true } /** - * Declare a strict [NamedComponent] dependency with [name] and type [C], + * Declare a strict [INamedComponent] dependency with [name] and type [C], * * @return the dependency declaration */ -inline fun > +inline fun > DependencyManager.dependency(name: String) = dependOnStrict(C::class) { it.name == name } /** - * Declare a weak [UniqueComponent] dependency with type [C], + * Declare a weak [IUniqueComponent] dependency with type [C], * * @return the dependency declaration */ -inline fun > +inline fun > DependencyManager.weakDependency() = dependOnWeak(C::class) { true } /** - * Declare a weak [NamedComponent] dependency with [name] and type [C], + * Declare a weak [INamedComponent] dependency with [name] and type [C], * * @return the dependency declaration */ -inline fun > +inline fun > DependencyManager.weakDependency(name: String) = dependOnWeak(C::class) { it.name == name } /** - * Declare a strict [UniqueComponent] dependency with type [C], + * Declare a strict [IUniqueComponent] dependency with type [C], * creating a lazy initialized delegate that obtains its value via [block] * * @return a property delegate using [lazy] */ -inline fun , T> +inline fun , T> DependencyManager.mustUnique(crossinline block: (C) -> T) = must({ true }, block) /** - * Declare a strict [NamedComponent] dependency with [name] and type [C], + * Declare a strict [INamedComponent] dependency with [name] and type [C], * creating a lazy initialized delegate that obtains its value via [block] * * @return a property delegate using [lazy] */ -inline fun , T> +inline fun , T> DependencyManager.mustNamed(name: String, crossinline block: (C) -> T) = must({ it.name == name }, block) /** - * Declare a weak [UniqueComponent] dependency with type [C], + * Declare a weak [IUniqueComponent] dependency with type [C], * creating a lazy initialized delegate that obtains its value via [block] * * @return a property delegate using [lazy] */ -inline fun , T> +inline fun , T> DependencyManager.maybe(default: T, crossinline block: (C) -> T) = maybe({ true }, default, block) /** - * Declare a weak [NamedComponent] dependency with [name] and type [C], + * Declare a weak [INamedComponent] dependency with [name] and type [C], * creating a lazy initialized delegate that obtains its value via [block] * * @return a property delegate using [lazy] */ -inline fun , T> +inline fun , T> DependencyManager.maybe(name: String, default: T, crossinline block: (C) -> T) = maybe({ it.name == name }, default, block) /** - * Declare a strict [UniqueComponent] dependency with type [C], + * Declare a strict [IUniqueComponent] dependency with type [C], * creating a delegate that obtains its value * * @return a property delegate */ -inline fun > +inline fun > DependencyManager.must() = must { true } /** - * Declare a strict [NamedComponent] dependency with [name] and type [C], + * Declare a strict [INamedComponent] dependency with [name] and type [C], * creating a delegate that obtains its value * * @return a property delegate */ -inline fun > +inline fun > DependencyManager.must(name: String) = must { it.name == name } /** - * Declare a weak [UniqueComponent] dependency with type [C], + * Declare a weak [IUniqueComponent] dependency with type [C], * creating a delegate that obtains its value * * @return a property delegate */ -inline fun > +inline fun > DependencyManager.maybe() = maybe { true } /** - * Declare a weak [NamedComponent] dependency with [name] and type [C], + * Declare a weak [INamedComponent] dependency with [name] and type [C], * creating a delegate that obtains its value * * @return a property delegate */ -inline fun > +inline fun > DependencyManager.maybe(name: String) = maybe { it.name == name } + +/** + * Declare a strict [UniqueComponentWrapper] dependency with type [C] that wrappers type [T], + * creating a delegate that obtains its value + * + * @return a property delegate + */ +inline fun , reified T> + DependencyManager.mustWrapped() = must({ it.type == T::class }) { it.wrapped } + +/** + * Declare a weak [UniqueComponentWrapper] dependency with type [C] that wrappers type [T], + * creating a delegate that obtains its value + * + * @return a property delegate + */ +inline fun , reified T> + DependencyManager.maybeWrapped() = must({ it.type == T::class }) { it.wrapped } + +/** + * Wrap [this] to a unique component + */ +inline fun T.wrapToUniqueComponent() = UniqueComponentWrapper(this) \ No newline at end of file diff --git a/src/test/kotlin/org/mechdancer/dependency/TestFindGeneric.kt b/src/test/kotlin/org/mechdancer/dependency/TestFindGeneric.kt new file mode 100644 index 0000000..2d91aa9 --- /dev/null +++ b/src/test/kotlin/org/mechdancer/dependency/TestFindGeneric.kt @@ -0,0 +1,55 @@ +package org.mechdancer.dependency + +import org.junit.Assert +import org.junit.Test + +abstract class G1> + +open class G2 : G1() + +interface G3, V> + +class G4 : G3 + +class G5 : G3, V> + +class G6 : G1() + +abstract class G7> : G1() + +class G8 : G7() + +class TestFindGeneric { + @Test + fun test() { + val g2 = G2() + Assert.assertEquals( + G2::class, + g2.javaClass.kotlin.findSuperGenericTypeRecursively(G1::class) + ) + val g4 = G4() + Assert.assertEquals( + G4::class, + g4.javaClass.kotlin.findSuperGenericTypeRecursively(G3::class) + ) + val g5 = G5() + Assert.assertEquals( + G5::class, + g5.javaClass.kotlin.findSuperGenericTypeRecursively(G3::class) + ) + val g6 = G6() + Assert.assertEquals( + Void::class, + g6.javaClass.kotlin.findSuperGenericTypeRecursively(G1::class) + ) + val g8 = G8() + Assert.assertEquals( + G8::class, + g8.javaClass.kotlin.findSuperGenericTypeRecursively(G1::class) + ) + Assert.assertEquals( + G8::class, + g8.javaClass.kotlin.findSuperGenericTypeRecursively(G7::class) + ) + } +} \ No newline at end of file diff --git a/src/test/kotlin/org/mechdancer/dependency/TestWrapped.kt b/src/test/kotlin/org/mechdancer/dependency/TestWrapped.kt new file mode 100644 index 0000000..9f4b884 --- /dev/null +++ b/src/test/kotlin/org/mechdancer/dependency/TestWrapped.kt @@ -0,0 +1,69 @@ +package org.mechdancer.dependency + +import org.junit.Assert +import org.junit.Test +import org.mechdancer.dependency.annotated.Must +import org.mechdancer.dependency.annotated.Unwrap +import org.mechdancer.dependency.annotated.annotatedInjector +import org.mechdancer.dependency.manager.ManagedHandler +import org.mechdancer.dependency.manager.managedHandler +import org.mechdancer.dependency.manager.mustWrapped +import org.mechdancer.dependency.manager.wrapToUniqueComponent + +open class Common + +class J : Common() + +class K : UniqueComponent(), Dependent, ManagedHandler by managedHandler() { + val j: J by manager.mustWrapped() + val int: Int by manager.mustWrapped() + val m: M by manager.mustWrapped() + val common: Common by manager.mustWrapped() +} + +class L : UniqueComponent(), Dependent { + private val injector by annotatedInjector() + + @Unwrap + @Must + lateinit var j: J + + @Unwrap + @Must + var int: Int? = null + + @Unwrap + @Must + var common: Common? = null + + override fun handle(scopeEvent: ScopeEvent) = injector.handle(scopeEvent) +} + +class M : Common() + +class TestWrapped { + @Test + fun test() { + val j = J() + val k = K() + val int = 233 + val l = L() + val common = Common() + val m = M() + scope { + this += j.wrapToUniqueComponent() + this += int.wrapToUniqueComponent() + this += k + this += l + this += common.wrapToUniqueComponent() + this += m.wrapToUniqueComponent() + } + Assert.assertEquals(j, k.j) + Assert.assertEquals(int, k.int) + Assert.assertEquals(j, l.j) + Assert.assertEquals(int, l.int) + Assert.assertEquals(m, k.m) + Assert.assertEquals(common, l.common) + Assert.assertEquals(common, k.common) + } +} \ No newline at end of file