mirror of
https://github.com/jimeh/casecmp.git
synced 2026-02-19 02:16:40 +00:00
318 lines
8.3 KiB
Go
318 lines
8.3 KiB
Go
// Copyright 2016 Qiang Xue. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package routing
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// store is a radix tree that supports storing data with parametric keys and retrieving them back with concrete keys.
|
|
// When retrieving a data item with a concrete key, the matching parameter names and values will be returned as well.
|
|
// A parametric key is a string containing tokens in the format of "<name>", "<name:pattern>", or "<:pattern>".
|
|
// Each token represents a single parameter.
|
|
type store struct {
|
|
root *node // the root node of the radix tree
|
|
count int // the number of data nodes in the tree
|
|
}
|
|
|
|
// newStore creates a new store.
|
|
func newStore() *store {
|
|
return &store{
|
|
root: &node{
|
|
static: true,
|
|
children: make([]*node, 256),
|
|
pchildren: make([]*node, 0),
|
|
pindex: -1,
|
|
pnames: []string{},
|
|
},
|
|
}
|
|
}
|
|
|
|
// Add adds a new data item with the given parametric key.
|
|
// The number of parameters in the key is returned.
|
|
func (s *store) Add(key string, data interface{}) int {
|
|
s.count++
|
|
return s.root.add(key, data, s.count)
|
|
}
|
|
|
|
// Get returns the data item matching the given concrete key.
|
|
// If the data item was added to the store with a parametric key before, the matching
|
|
// parameter names and values will be returned as well.
|
|
func (s *store) Get(path string, pvalues []string) (data interface{}, pnames []string) {
|
|
data, pnames, _ = s.root.get(path, pvalues)
|
|
return
|
|
}
|
|
|
|
// String dumps the radix tree kept in the store as a string.
|
|
func (s *store) String() string {
|
|
return s.root.print(0)
|
|
}
|
|
|
|
// node represents a radix trie node
|
|
type node struct {
|
|
static bool // whether the node is a static node or param node
|
|
|
|
key string // the key identifying this node
|
|
data interface{} // the data associated with this node. nil if not a data node.
|
|
|
|
order int // the order at which the data was added. used to be pick the first one when matching multiple
|
|
minOrder int // minimum order among all the child nodes and this node
|
|
|
|
children []*node // child static nodes, indexed by the first byte of each child key
|
|
pchildren []*node // child param nodes
|
|
|
|
regex *regexp.Regexp // regular expression for a param node containing regular expression key
|
|
pindex int // the parameter index, meaningful only for param node
|
|
pnames []string // the parameter names collected from the root till this node
|
|
}
|
|
|
|
// add adds a new data item to the tree rooted at the current node.
|
|
// The number of parameters in the key is returned.
|
|
func (n *node) add(key string, data interface{}, order int) int {
|
|
matched := 0
|
|
|
|
// find the common prefix
|
|
for ; matched < len(key) && matched < len(n.key); matched++ {
|
|
if key[matched] != n.key[matched] {
|
|
break
|
|
}
|
|
}
|
|
|
|
if matched == len(n.key) {
|
|
if matched == len(key) {
|
|
// the node key is the same as the key: make the current node as data node
|
|
// if the node is already a data node, ignore the new data since we only care the first matched node
|
|
if n.data == nil {
|
|
n.data = data
|
|
n.order = order
|
|
}
|
|
return n.pindex + 1
|
|
}
|
|
|
|
// the node key is a prefix of the key: create a child node
|
|
newKey := key[matched:]
|
|
|
|
// try adding to a static child
|
|
if child := n.children[newKey[0]]; child != nil {
|
|
if pn := child.add(newKey, data, order); pn >= 0 {
|
|
return pn
|
|
}
|
|
}
|
|
// try adding to a param child
|
|
for _, child := range n.pchildren {
|
|
if pn := child.add(newKey, data, order); pn >= 0 {
|
|
return pn
|
|
}
|
|
}
|
|
|
|
return n.addChild(newKey, data, order)
|
|
}
|
|
|
|
if matched == 0 || !n.static {
|
|
// no common prefix, or partial common prefix with a non-static node: should skip this node
|
|
return -1
|
|
}
|
|
|
|
// the node key shares a partial prefix with the key: split the node key
|
|
n1 := &node{
|
|
static: true,
|
|
key: n.key[matched:],
|
|
data: n.data,
|
|
order: n.order,
|
|
minOrder: n.minOrder,
|
|
pchildren: n.pchildren,
|
|
children: n.children,
|
|
pindex: n.pindex,
|
|
pnames: n.pnames,
|
|
}
|
|
|
|
n.key = key[0:matched]
|
|
n.data = nil
|
|
n.pchildren = make([]*node, 0)
|
|
n.children = make([]*node, 256)
|
|
n.children[n1.key[0]] = n1
|
|
|
|
return n.add(key, data, order)
|
|
}
|
|
|
|
// addChild creates static and param nodes to store the given data
|
|
func (n *node) addChild(key string, data interface{}, order int) int {
|
|
// find the first occurrence of a param token
|
|
p0, p1 := -1, -1
|
|
for i := 0; i < len(key); i++ {
|
|
if p0 < 0 && key[i] == '<' {
|
|
p0 = i
|
|
}
|
|
if p0 >= 0 && key[i] == '>' {
|
|
p1 = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if p0 > 0 && p1 > 0 || p1 < 0 {
|
|
// param token occurs after a static string, or no param token: create a static node
|
|
child := &node{
|
|
static: true,
|
|
key: key,
|
|
minOrder: order,
|
|
children: make([]*node, 256),
|
|
pchildren: make([]*node, 0),
|
|
pindex: n.pindex,
|
|
pnames: n.pnames,
|
|
}
|
|
n.children[key[0]] = child
|
|
if p1 > 0 {
|
|
// param token occurs after a static string
|
|
child.key = key[:p0]
|
|
n = child
|
|
} else {
|
|
// no param token: done adding the child
|
|
child.data = data
|
|
child.order = order
|
|
return child.pindex + 1
|
|
}
|
|
}
|
|
|
|
// add param node
|
|
child := &node{
|
|
static: false,
|
|
key: key[p0 : p1+1],
|
|
minOrder: order,
|
|
children: make([]*node, 256),
|
|
pchildren: make([]*node, 0),
|
|
pindex: n.pindex,
|
|
pnames: n.pnames,
|
|
}
|
|
pattern := ""
|
|
pname := key[p0+1 : p1]
|
|
for i := p0 + 1; i < p1; i++ {
|
|
if key[i] == ':' {
|
|
pname = key[p0+1 : i]
|
|
pattern = key[i+1 : p1]
|
|
break
|
|
}
|
|
}
|
|
if pattern != "" {
|
|
// the param token contains a regular expression
|
|
child.regex = regexp.MustCompile("^" + pattern)
|
|
}
|
|
pnames := make([]string, len(n.pnames)+1)
|
|
copy(pnames, n.pnames)
|
|
pnames[len(n.pnames)] = pname
|
|
child.pnames = pnames
|
|
child.pindex = len(pnames) - 1
|
|
n.pchildren = append(n.pchildren, child)
|
|
|
|
if p1 == len(key)-1 {
|
|
// the param token is at the end of the key
|
|
child.data = data
|
|
child.order = order
|
|
return child.pindex + 1
|
|
}
|
|
|
|
// process the rest of the key
|
|
return child.addChild(key[p1+1:], data, order)
|
|
}
|
|
|
|
// get returns the data item with the key matching the tree rooted at the current node
|
|
func (n *node) get(key string, pvalues []string) (data interface{}, pnames []string, order int) {
|
|
order = math.MaxInt32
|
|
|
|
repeat:
|
|
if n.static {
|
|
// check if the node key is a prefix of the given key
|
|
// a slightly optimized version of strings.HasPrefix
|
|
nkl := len(n.key)
|
|
if nkl > len(key) {
|
|
return
|
|
}
|
|
for i := nkl - 1; i >= 0; i-- {
|
|
if n.key[i] != key[i] {
|
|
return
|
|
}
|
|
}
|
|
key = key[nkl:]
|
|
} else if n.regex != nil {
|
|
// param node with regular expression
|
|
if n.regex.String() == "^.*" {
|
|
pvalues[n.pindex] = key
|
|
key = ""
|
|
} else if match := n.regex.FindStringIndex(key); match != nil {
|
|
pvalues[n.pindex] = key[0:match[1]]
|
|
key = key[match[1]:]
|
|
} else {
|
|
return
|
|
}
|
|
} else {
|
|
// param node matching non-"/" characters
|
|
i, kl := 0, len(key)
|
|
for ; i < kl; i++ {
|
|
if key[i] == '/' {
|
|
pvalues[n.pindex] = key[0:i]
|
|
key = key[i:]
|
|
break
|
|
}
|
|
}
|
|
if i == kl {
|
|
pvalues[n.pindex] = key
|
|
key = ""
|
|
}
|
|
}
|
|
|
|
if len(key) > 0 {
|
|
// find a static child that can match the rest of the key
|
|
if child := n.children[key[0]]; child != nil {
|
|
if len(n.pchildren) == 0 {
|
|
// use goto to avoid recursion when no param children
|
|
n = child
|
|
goto repeat
|
|
}
|
|
data, pnames, order = child.get(key, pvalues)
|
|
}
|
|
} else if n.data != nil {
|
|
// do not return yet: a param node may match an empty string with smaller order
|
|
data, pnames, order = n.data, n.pnames, n.order
|
|
}
|
|
|
|
// try matching param children
|
|
tvalues := pvalues
|
|
allocated := false
|
|
for _, child := range n.pchildren {
|
|
if child.minOrder >= order {
|
|
continue
|
|
}
|
|
if data != nil && !allocated {
|
|
tvalues = make([]string, len(pvalues))
|
|
allocated = true
|
|
}
|
|
if d, p, s := child.get(key, tvalues); d != nil && s < order {
|
|
if allocated {
|
|
for i := child.pindex; i < len(p); i++ {
|
|
pvalues[i] = tvalues[i]
|
|
}
|
|
}
|
|
data, pnames, order = d, p, s
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (n *node) print(level int) string {
|
|
r := fmt.Sprintf("%v{key: %v, regex: %v, data: %v, order: %v, minOrder: %v, pindex: %v, pnames: %v}\n", strings.Repeat(" ", level<<2), n.key, n.regex, n.data, n.order, n.minOrder, n.pindex, n.pnames)
|
|
for _, child := range n.children {
|
|
if child != nil {
|
|
r += child.print(level + 1)
|
|
}
|
|
}
|
|
for _, child := range n.pchildren {
|
|
r += child.print(level + 1)
|
|
}
|
|
return r
|
|
}
|