
commonMain.com.ditchoom.mqtt.topic.Node.kt Maven / Gradle / Ivy
@file:Suppress("EXPERIMENTAL_API_USAGE")
package com.ditchoom.mqtt.topic
import com.ditchoom.mqtt.controlpacket.IPublishMessage
import com.ditchoom.mqtt.controlpacket.MqttUtf8String
import com.ditchoom.mqtt.controlpacket.QualityOfService
data class Node internal constructor(val level: TopicLevel) {
val children = mutableMapOf()
private var parent: Node? = null
private val isRoot = level.value == ROOT_TOPIC_NODE_VALUE
val isWildcard = level is LevelWildcard
private val callbacks = ArrayList>()
fun addCallback(callback: CallbackTypeReference) {
callbacks += callback
}
fun removeCallback(callback: CallbackTypeReference) {
callbacks -= callback
}
fun handlePublish(msg: IPublishMessage) {
callbacks.forEach { it.handleCallback(msg) }
}
override fun toString(): String {
if (isRoot) {
return "ROOT NODE W/CHILDREN: ${allChildren()}"
}
val parent = parent!! // if we are not root we have a parent root
return if (parent.isRoot) {
level.value.toString()
} else {
(parent.toString()) + TOPIC_SEPERATOR + level.value
}
}
fun addChild(node: Node) {
children[node.level] = node
node.parent = this
}
fun find(otherTopic: Name): Node? {
var currentLocalNode = root()
otherTopic.validate() ?: throw IllegalArgumentException("$otherTopic is an invalid topic name")
// even though we have a topic node, there is no way to have multiple nodes for the validatedOtherTopic
val otherTopicNameSplit = otherTopic.topic.split(TOPIC_SEPERATOR)
for (otherLevel in otherTopicNameSplit) {
if (currentLocalNode.level is MultiLevelWildcard) {
return currentLocalNode
}
val childNodeMatch = currentLocalNode.children[otherLevel.toTopicLevel()]
?: currentLocalNode.children[MultiLevelWildcard]
?: currentLocalNode.children[SingleLevelWildcard]
?: return null
currentLocalNode = childNodeMatch
}
return currentLocalNode
}
fun allChildren(): Set {
val childrenLocal = mutableSetOf()
for (child in children.values) {
childrenLocal += child.allChildren()
}
if (children.isEmpty()) {
childrenLocal += this
}
return childrenLocal
}
fun matchesTopic(otherTopic: Node) = find(Name(otherTopic.toString())) != null
fun root(): Node = parent?.root() ?: this
fun detachFromParent() {
parent?.children?.remove(level)
parent = null
}
companion object {
private const val TOPIC_SEPERATOR = '/'
internal const val ROOT_TOPIC_NODE_VALUE = "\$SYS_ROOT_TOPIC_DEFAULT"
fun from(topic: MqttUtf8String) = parse(topic.getValueOrThrow())
fun parse(topic: CharSequence): Node {
val rootNode = Node(RootTopicValue)
if (topic.isEmpty()) {
val emptyNode = Node(EmptyValue)
rootNode.addChild(emptyNode)
return emptyNode
}
var currentNode = rootNode
val topicsSplit = topic.split(TOPIC_SEPERATOR)
for (topicLevelString in topicsSplit) {
val child = Node(topicLevelString.toTopicLevel())
currentNode.addChild(child)
currentNode = child
}
return currentNode
}
fun newRootNode() = Node(ROOT_TOPIC_NODE_VALUE.toTopicLevel())
}
}
fun Node.addName(other: Name): Node {
val node = Node(other.topic.toTopicLevel())
this += node
return node
}
operator fun Node.plusAssign(other: Node) {
for ((topicLevel, newChildNode) in other.children) {
val originalChild = children[topicLevel]
if (originalChild != null) {
originalChild += newChildNode
continue
}
addChild(newChildNode)
}
}
operator fun Node.minusAssign(other: Node) {
val foundChildNode = find(Name(other.toString()))
foundChildNode?.detachFromParent()
}
interface SubscriptionCallback {
fun onMessageReceived(topic: Name, qos: QualityOfService, message: T?)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy