Skip to content

Instantly share code, notes, and snippets.

@anhldbk
Created November 21, 2016 08:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anhldbk/253e4772189c1dba6aada3c49a7ade3e to your computer and use it in GitHub Desktop.
Save anhldbk/253e4772189c1dba6aada3c49a7ade3e to your computer and use it in GitHub Desktop.
Module for matching MQTT-like topics
'use strict'
const _ = require('lodash'),
path = require('path')
/**
* Class for matching MQTT-like topics
*/
class EventTree {
constructor() {
this.root = new TopicNode()
}
/**
* Add an event
* @param {String} event The event. For example: `+/system/+`, `system/data`...
*
* An event is valid if it follow below rules:
* - Rule #1: If any part of the event is not `+` or `#`, then it must not contain `+` and '#'
* - Rule #2: Part `#` must be located at the end
*
* @param {Object} context Any data associated with the event
* @return {Boolean} If the event is valid, returns true. Otherwise, returns false.
*/
add(event, context) {
var parts = event.split('/'),
i = 0
// validate topics
i = _.findIndex(parts, (p) => {
if (p.length == 0) { // empty part
return true
}
if ('+' !== p) {
if ('#' === p) {
if (i != parts.length - 1) {
// for Rule #2
return true
}
} else {
if (_.findIndex(p, m => (m === '#' | m === '+' | m === '')) != -1) {
return true
}
}
}
i += 1
})
if (i != -1) {
return false
}
_.reduce(parts, (node, part) => node.addChildNode(part, context), this.root)
return true
}
/**
* Match an event with pre-added topics
* @param {String} event The event which does NOT contain characters `+` and `#`
* @return {Array} If the event is valid, returns array of matched topics
* Otherwise, returns an empty array
*/
match(event) {
var result = this._getNodes(event)
return _.map(result, node => node.path)
}
/**
* Get nodes associated with an event
* @param {String} event Event name
* @return {Array} Array of TreeNode
*/
_getNodes(event) {
var parts = event.split('/'),
valid = _.findIndex(parts, m => (m === '#' | m === '+' | m === '')),
result = []
if (!valid) {
//Invalid event. Must not contain characters `#` or `+`'
return []
}
// recursively check
result = _.reduce(
parts,
(nodes, part) => {
var result = _.flatten(
_.map(
nodes,
(node) => node.isAny() ? node : node.matchChildNodes(part)
)
)
return result
}, [this.root]
)
// keep only leaf nodes
result = _.filter(result, node => node.isLeaf())
return result
}
/**
* Get contexts associated with a specific event
* @param {String} event Event name
* @return {Array} Array of contexts
*/
getContexts(event) {
var result = this._getNodes(event)
return _.flatten(
_.map(result, node => node.contexts)
)
}
}
class TopicNode {
constructor(label, parent, context) {
(_.isNil(label)) && (label = '')
if (parent instanceof TopicNode) {
this.path = path.join(parent.path, label)
} else {
this.path = label
}
this.label = label
this._isAny = (label == '#')
this.children = {}
if (_.isNil(context)) {
context = []
}
if (!_.isArray(context)) {
context = [context]
}
this.contexts = context
}
/**
* Add a new child node
* @param {String} label Its label
* @param {Object} context Its context
* @return {TopicNode} If there's a child having the same label, returns that one.
* We also add more context to that node.
* Otherwise, a new node will be created and return
*/
addChildNode(label, context) {
if (!_.isString(label) || label.length === 0) {
throw new Error('Invalid label')
}
var node = _.get(this.children, label)
if (_.isNil(node)) {
node = new TopicNode(label, this, context)
this.children[node.label] = node
} else {
(!_.isNil(context)) && (node.contexts.push(context))
}
return node
}
matchChildNodes(label) {
var candidate = ['#', '+'],
result = []
if (_.indexOf(candidate, label) == -1) {
candidate.push(label)
}
_.forEach(candidate, (c) => {
if (_.has(this.children, c)) {
result.push(this.children[c])
}
})
return result
}
isLeaf() {
return _.values(this.children).length == 0
}
isAny() {
return this._isAny
}
}
module.exports = EventTree
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment