Skip to content

Commit

Permalink
Merge pull request #3 from MechDancer/teardown
Browse files Browse the repository at this point in the history
Support removing components from scope
  • Loading branch information
YdrMaster authored Mar 9, 2022
2 parents dbbd57b + 1ae93ac commit 847dd69
Show file tree
Hide file tree
Showing 16 changed files with 265 additions and 97 deletions.
13 changes: 0 additions & 13 deletions src/main/kotlin/org/mechdancer/dependency/DependencyHandler.kt

This file was deleted.

6 changes: 1 addition & 5 deletions src/main/kotlin/org/mechdancer/dependency/Dependent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,5 @@ package org.mechdancer.dependency

/**
* [Dependent] is a type of [Component] which needs other components as dependencies
*
* [handle] will be called when a new component arrives [DynamicScope], until all dependencies are retrieved.
*/
interface Dependent : Component, DependencyHandler {
override fun handle(dependency: Component): Boolean
}
interface Dependent : Component, ScopeEventHandler
47 changes: 24 additions & 23 deletions src/main/kotlin/org/mechdancer/dependency/DynamicScope.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.mechdancer.dependency

import org.mechdancer.dependency.utils.ConcurrentHashSet
import java.util.concurrent.ConcurrentLinkedQueue

/**
* [DynamicScope] is a scope that accepts components dynamically
Expand All @@ -10,48 +9,50 @@ import java.util.concurrent.ConcurrentLinkedQueue
open class DynamicScope {
/**
* Component set
* We look up specific types of components from here.
* There's no chance to remove a component.
*/
private val _components = ConcurrentHashSet<Component>()

/**
* Dependent list
* Dependents' [Dependent.handle] will be called once new component arrives.
* Dependents will be removed from the list once they are satisfied, i.e., their [Dependent.handle]
* return `true`.
*/
private val dependents = ConcurrentLinkedQueue<(Component) -> Boolean>()
protected val components = ConcurrentHashSet<Component>()

/**
* Get a view of all components
*/
val components = _components.view
fun viewComponents() = components.view

/**
* Add a new [component] to the scope
*
* @return If [component] is already in scope
*/
open infix fun setup(component: Component) =
_components
components
.add(component)
.also {
// Update the status of dependents
if (it)
dependents.removeIf { it(component) }
components.forEach {
if (it is Dependent)
it.handle(ScopeEvent.DependencyArrivedEvent(component))
if (component is Dependent)
component.handle(ScopeEvent.DependencyArrivedEvent(it))
}
}

if (component is Dependent)
component::handle
.takeIf { handle -> _components.none(handle) }
?.let(dependents::add)
/**
* Remove [component] from the scope
*
* @return If [component] is in the scope
*/
open infix fun teardown(component: Component) =
components
.remove(component)
.also {
components.forEach {
if (it is Dependent)
it.handle(ScopeEvent.DependencyLeftEvent(component))
}
}

/**
* Clear everything
*/
fun clear() {
_components.clear()
dependents.clear()
components.clear()
}
}
9 changes: 8 additions & 1 deletion src/main/kotlin/org/mechdancer/dependency/Functions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ operator fun DynamicScope.plusAssign(component: Component) {
setup(component)
}

/**
* Operator of [DynamicScope.teardown]
*/
operator fun DynamicScope.minusAssign(component: Component) {
teardown(component)
}

/**
* Add a [component] to the scope, executing [block] if success
*/
Expand All @@ -49,7 +56,7 @@ fun DynamicScope.setupRecursively(root: TreeComponent) {
/**
* Create a [DynamicScope]
*/
fun scope(block: DynamicScope.() -> Unit) =
inline fun scope(block: DynamicScope.() -> Unit) =
DynamicScope().apply(block)

/**
Expand Down
18 changes: 18 additions & 0 deletions src/main/kotlin/org/mechdancer/dependency/ScopeEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.mechdancer.dependency

/**
* Events of a [DynamicScope]
*/
sealed interface ScopeEvent {
/**
* A new component was added to the scope
*/
@JvmInline
value class DependencyArrivedEvent(val dependency: Component) : ScopeEvent

/**
* A component was removed from the scope
*/
@JvmInline
value class DependencyLeftEvent(val dependency: Component) : ScopeEvent
}
11 changes: 11 additions & 0 deletions src/main/kotlin/org/mechdancer/dependency/ScopeEventHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.mechdancer.dependency

/**
* [ScopeEventHandler] handles [ScopeEvent]
*/
interface ScopeEventHandler {
/**
* Handle a [scopeEvent]
*/
fun handle(scopeEvent: ScopeEvent)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.mechdancer.dependency

/**
* [TeardownStrictDependencyException] is the exception indicates that
* a component which was depended on strictly was being removed from the scope
*/
class TeardownStrictDependencyException(
toRemove: Component,
dependency: TypeSafeDependency.Dependency<*>
) : RuntimeException("Cannot teardown $toRemove from scope, $dependency depends strictly on this")
44 changes: 36 additions & 8 deletions src/main/kotlin/org/mechdancer/dependency/TypeSafeDependency.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,54 @@ sealed class TypeSafeDependency<T : Component>(
val type: KClass<T>,
private val predicate: (T) -> Boolean
) {
private val _field = AtomicReference<T?>(null)
private var onSetListener: ((T) -> Unit)? = null

protected val fieldRef = AtomicReference<T?>(null)

/**
* Try to get the value
*/
fun fieldOrNull() = fieldRef.get()

/**
* Try to set [value]
*
* Fail if unable to cast [value] to desired type or the predication fails
*/
fun set(value: Component): T? =
_field.updateAndGet {
fieldRef.updateAndGet {
type.safeCast(value)?.takeIf(predicate) ?: it
}
}?.also { onSetListener?.invoke(it) }

/**
* Try to get the value
* Called when [fieldRef] was successfully set
*/
open val field: T? get() = _field.get()
fun setOnSetListener(listener: ((T) -> Unit)? = null) {
onSetListener = listener
}

/**
* Weak dependency with type [T]
*/
class WeakDependency<T : Component>(type: KClass<T>, predicate: (T) -> Boolean) :
TypeSafeDependency<T>(type, predicate)
TypeSafeDependency<T>(type, predicate) {
private var onClearListener: (() -> Unit)? = null

/**
* Called when [fieldRef] was successfully cleared
*/
fun setOnClearListener(listener: (() -> Unit)? = null) {
onClearListener = listener
}

/**
* Clear [fieldRef]
*/
fun clear() {
fieldRef.set(null)
onClearListener?.invoke()
}
}

/**
* Strict dependency with type [T]
Expand All @@ -46,9 +72,11 @@ sealed class TypeSafeDependency<T : Component>(
* Try to get the value
* @throws ComponentNotExistException if unable to get
*/
override val field: T get() = super.field ?: throw ComponentNotExistException(type)
val field: T get() = super.fieldOrNull() ?: throw ComponentNotExistException(type)
}

override fun equals(other: Any?) = this === other || (other as? TypeSafeDependency<*>)?.type == type
override fun equals(other: Any?) =
this === other || (other as? TypeSafeDependency<*>)?.type == type

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

import org.mechdancer.dependency.Component
import org.mechdancer.dependency.DependencyHandler
import org.mechdancer.dependency.NamedComponent
import org.mechdancer.dependency.TypeSafeDependency
import java.lang.reflect.Field
import java.util.concurrent.ConcurrentLinkedQueue
import org.mechdancer.dependency.*
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.full.declaredMemberProperties
Expand All @@ -16,14 +11,16 @@ import kotlin.reflect.jvm.jvmErasure
* Annotation style dependency manager
*/
@Suppress("UNCHECKED_CAST")
class AnnotatedInjector<T : Any>(private val dependent: T, type: KClass<T>) : DependencyHandler {
class AnnotatedInjector<T : Any>(private val dependent: T, type: KClass<T>) : ScopeEventHandler {

private val fields = ConcurrentLinkedQueue<Pair<TypeSafeDependency<*>, Field>>()
private val dependencies = mutableListOf<TypeSafeDependency<*>>()

init {
val allFields = type.declaredMemberProperties

fun KProperty1<*, *>.getName() = javaField!!.annotations.firstNotNullOfOrNull { it as? Name }?.name
fun KProperty1<*, *>.getName() =
javaField!!.annotations.firstNotNullOfOrNull { it as? Name }?.name

fun Component.toPredicate(name: String?) =
if (this is NamedComponent<*>)
name == this.name
Expand All @@ -35,36 +32,67 @@ class AnnotatedInjector<T : Any>(private val dependent: T, type: KClass<T>) : De
val name = it.getName()
TypeSafeDependency.Dependency(it.returnType.jvmErasure as KClass<out Component>) { component ->
component.toPredicate(name)
} to it.javaField!!
}.also { dep ->
dep.setOnSetListener { component ->
it.javaField!!.apply {
isAccessible = true
set(dependent, component)
}
}
}
}
.let {
fields.addAll(it)
dependencies.addAll(it)
}

allFields
// prioritize non-null fields
.filter { it.javaField?.isAnnotationPresent(Maybe::class.java) ?: false }
.sortedBy { it.returnType.isMarkedNullable }
.map {
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)
} to it.javaField!!
}.also { dep ->
dep.setOnSetListener { component ->
it.javaField!!.apply {
isAccessible = true
set(dependent, component)
}
}
dep.setOnClearListener {
it.javaField!!.set(dependent, null)
}
}
}
.let { fields.addAll(it) }
.let { dependencies.addAll(it) }

}

override fun handle(dependency: Component): Boolean {
fields.removeIf { (k, v) ->
k.set(dependency)?.let {
runCatching {
v.isAccessible = true
v.set(dependent, dependency)
}.map { true }.getOrElse { false }
} ?: false

override fun handle(scopeEvent: ScopeEvent) {
when (scopeEvent) {
is ScopeEvent.DependencyArrivedEvent -> {
dependencies.forEach {
if (it.fieldOrNull() == null)
it.set(scopeEvent.dependency)
}
}
is ScopeEvent.DependencyLeftEvent -> {
dependencies.filter {
it.fieldOrNull() == scopeEvent.dependency
}.forEach {
when (it) {
is TypeSafeDependency.Dependency -> throw TeardownStrictDependencyException(
scopeEvent.dependency,
it
)
is TypeSafeDependency.WeakDependency -> it.clear()
}
}
}
}
return fields.isEmpty()
}

}
15 changes: 13 additions & 2 deletions src/main/kotlin/org/mechdancer/dependency/annotated/Functions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@ package org.mechdancer.dependency.annotated

import org.mechdancer.dependency.Dependent
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

/**
* Create a property delegate of [AnnotatedInjector]
*/
inline fun <reified T : Dependent> annotatedInjector() =
ReadOnlyProperty<T, AnnotatedInjector<T>> { thisRef, _ ->
AnnotatedInjector(thisRef, T::class)
object : ReadOnlyProperty<T, AnnotatedInjector<T>> {
@Volatile
private var injector: AnnotatedInjector<T>? = null

override fun getValue(thisRef: T, property: KProperty<*>): AnnotatedInjector<T> {
if (injector != null)
return injector!!
synchronized(this) {
injector = AnnotatedInjector(thisRef, T::class)
return injector!!
}
}
}

Loading

0 comments on commit 847dd69

Please sign in to comment.