Files
cloudpods/pkg/appsrv/radix.go
2019-03-29 14:47:48 +08:00

196 lines
4.5 KiB
Go

// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package appsrv
import (
"fmt"
"path"
"regexp"
"strings"
"yunion.io/x/log"
)
type RadixNode struct {
data interface{}
stringNodes map[string]*RadixNode
regexpNodes []*RegexpNode
segNames map[int]string
}
type RegexpNode struct {
node *RadixNode
regStr string
}
func NewRadix() *RadixNode {
return &RadixNode{
data: nil,
stringNodes: make(map[string]*RadixNode, 0),
regexpNodes: make([]*RegexpNode, 0),
segNames: nil,
}
}
func NewRegexpNode(node *RadixNode, regStr string) *RegexpNode {
return &RegexpNode{
node: node,
regStr: regStr,
}
}
func isRegstrInRegexpNodes(regNodes []*RegexpNode, regStr string) (*RadixNode, bool) {
for i := 0; i < len(regNodes); i++ {
if regNodes[i].regStr == regStr {
return regNodes[i].node, true
}
}
return nil, false
}
func isRegexSegment(seg string) bool {
if len(seg) > 2 && seg[0] == '<' && seg[len(seg)-1] == '>' {
return true
} else {
return false
}
}
func (r *RadixNode) Add(segments []string, data interface{}) error {
err := r.add(segments, data, 1, nil)
if err != nil {
return fmt.Errorf("Add Node error: %s %s", err, strings.Join(segments, "/"))
}
return nil
}
func (r *RadixNode) add(segments []string, data interface{}, depth int, segNames map[int]string) error {
if len(segments) == 0 {
if r.data != nil {
return fmt.Errorf("Duplicate data for node")
} else {
r.data = data
r.segNames = segNames
return nil
}
} else {
var nextNode *RadixNode
if isRegexSegment(segments[0]) {
var (
regStr string
segName string
segStr = segments[0][1 : len(segments[0])-1]
splitIndex = strings.IndexByte(segStr, ':')
)
if splitIndex < 0 {
regStr = ".*" // match anything
segName = "<" + segStr + ">"
} else {
regStr = segStr[splitIndex+1:]
segName = "<" + segStr[0:splitIndex] + ">"
}
if segNames == nil {
segNames = make(map[int]string, 0)
}
segNames[depth-1] = segName
if node, ok := isRegstrInRegexpNodes(r.regexpNodes, regStr); ok {
nextNode = node
} else {
nextNode = NewRadix()
regNode := NewRegexpNode(nextNode, regStr)
r.regexpNodes = append(r.regexpNodes, regNode)
}
} else {
if node, ok := r.stringNodes[segments[0]]; ok {
nextNode = node
} else {
nextNode = NewRadix()
r.stringNodes[segments[0]] = nextNode
}
}
return nextNode.add(segments[1:], data, depth+1, segNames)
}
}
func (r *RadixNode) Match(segments []string, params map[string]string) interface{} {
node, _ := r.match(segments)
if node == nil {
return nil
}
for index, segName := range node.segNames {
params[segName] = segments[index]
}
return node.data
}
func (r *RadixNode) match(segments []string) (*RadixNode, bool) {
if len(segments) == 0 {
return r, true
} else if len(r.stringNodes) == 0 && len(r.regexpNodes) == 0 {
return r, false
}
if node, ok := r.stringNodes[segments[0]]; ok {
if rnode, _ := node.match(segments[1:]); rnode != nil && rnode.data != nil {
return rnode, true
}
}
var nodeTmp *RadixNode
for _, regNode := range r.regexpNodes {
if regexp.MustCompile(regNode.regStr).MatchString(segments[0]) {
if rnode, fullMatch := regNode.node.match(segments[1:]); rnode != nil && rnode.data != nil {
if fullMatch {
return rnode, fullMatch
} else {
if nodeTmp != nil {
log.Errorf("segments %v match mutil node", segments)
continue
}
nodeTmp = rnode
}
}
}
}
if nodeTmp != nil {
return nodeTmp, false
} else {
return r, false
}
}
func (r *RadixNode) Walk(f func(spath string, data interface{})) {
r.walk("/", f)
}
func (r *RadixNode) walk(fullPath string, f func(spath string, data interface{})) {
if r.data != nil {
f(fullPath, r.data)
}
for key, node := range r.stringNodes {
curPath := path.Join(fullPath, key)
node.walk(curPath, f)
}
for _, regNode := range r.regexpNodes {
curPath := path.Join(fullPath, regNode.regStr)
regNode.node.walk(curPath, f)
}
}