github.com/lingyao2333/mo-zero@v1.4.1/core/search/tree.go (about)

     1  package search
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  )
     7  
     8  const (
     9  	colon = ':'
    10  	slash = '/'
    11  )
    12  
    13  var (
    14  	// errDupItem means adding duplicated item.
    15  	errDupItem = errors.New("duplicated item")
    16  	// errDupSlash means item is started with more than one slash.
    17  	errDupSlash = errors.New("duplicated slash")
    18  	// errEmptyItem means adding empty item.
    19  	errEmptyItem = errors.New("empty item")
    20  	// errInvalidState means search tree is in an invalid state.
    21  	errInvalidState = errors.New("search tree is in an invalid state")
    22  	// errNotFromRoot means path is not starting with slash.
    23  	errNotFromRoot = errors.New("path should start with /")
    24  
    25  	// NotFound is used to hold the not found result.
    26  	NotFound Result
    27  )
    28  
    29  type (
    30  	innerResult struct {
    31  		key   string
    32  		value string
    33  		named bool
    34  		found bool
    35  	}
    36  
    37  	node struct {
    38  		item     interface{}
    39  		children [2]map[string]*node
    40  	}
    41  
    42  	// A Tree is a search tree.
    43  	Tree struct {
    44  		root *node
    45  	}
    46  
    47  	// A Result is a search result from tree.
    48  	Result struct {
    49  		Item   interface{}
    50  		Params map[string]string
    51  	}
    52  )
    53  
    54  // NewTree returns a Tree.
    55  func NewTree() *Tree {
    56  	return &Tree{
    57  		root: newNode(nil),
    58  	}
    59  }
    60  
    61  // Add adds item to associate with route.
    62  func (t *Tree) Add(route string, item interface{}) error {
    63  	if len(route) == 0 || route[0] != slash {
    64  		return errNotFromRoot
    65  	}
    66  
    67  	if item == nil {
    68  		return errEmptyItem
    69  	}
    70  
    71  	err := add(t.root, route[1:], item)
    72  	switch err {
    73  	case errDupItem:
    74  		return duplicatedItem(route)
    75  	case errDupSlash:
    76  		return duplicatedSlash(route)
    77  	default:
    78  		return err
    79  	}
    80  }
    81  
    82  // Search searches item that associates with given route.
    83  func (t *Tree) Search(route string) (Result, bool) {
    84  	if len(route) == 0 || route[0] != slash {
    85  		return NotFound, false
    86  	}
    87  
    88  	var result Result
    89  	ok := t.next(t.root, route[1:], &result)
    90  	return result, ok
    91  }
    92  
    93  func (t *Tree) next(n *node, route string, result *Result) bool {
    94  	if len(route) == 0 && n.item != nil {
    95  		result.Item = n.item
    96  		return true
    97  	}
    98  
    99  	for i := range route {
   100  		if route[i] != slash {
   101  			continue
   102  		}
   103  
   104  		token := route[:i]
   105  		return n.forEach(func(k string, v *node) bool {
   106  			r := match(k, token)
   107  			if !r.found || !t.next(v, route[i+1:], result) {
   108  				return false
   109  			}
   110  			if r.named {
   111  				addParam(result, r.key, r.value)
   112  			}
   113  
   114  			return true
   115  		})
   116  	}
   117  
   118  	return n.forEach(func(k string, v *node) bool {
   119  		if r := match(k, route); r.found && v.item != nil {
   120  			result.Item = v.item
   121  			if r.named {
   122  				addParam(result, r.key, r.value)
   123  			}
   124  
   125  			return true
   126  		}
   127  
   128  		return false
   129  	})
   130  }
   131  
   132  func (nd *node) forEach(fn func(string, *node) bool) bool {
   133  	for _, children := range nd.children {
   134  		for k, v := range children {
   135  			if fn(k, v) {
   136  				return true
   137  			}
   138  		}
   139  	}
   140  
   141  	return false
   142  }
   143  
   144  func (nd *node) getChildren(route string) map[string]*node {
   145  	if len(route) > 0 && route[0] == colon {
   146  		return nd.children[1]
   147  	}
   148  
   149  	return nd.children[0]
   150  }
   151  
   152  func add(nd *node, route string, item interface{}) error {
   153  	if len(route) == 0 {
   154  		if nd.item != nil {
   155  			return errDupItem
   156  		}
   157  
   158  		nd.item = item
   159  		return nil
   160  	}
   161  
   162  	if route[0] == slash {
   163  		return errDupSlash
   164  	}
   165  
   166  	for i := range route {
   167  		if route[i] != slash {
   168  			continue
   169  		}
   170  
   171  		token := route[:i]
   172  		children := nd.getChildren(token)
   173  		if child, ok := children[token]; ok {
   174  			if child != nil {
   175  				return add(child, route[i+1:], item)
   176  			}
   177  
   178  			return errInvalidState
   179  		}
   180  
   181  		child := newNode(nil)
   182  		children[token] = child
   183  		return add(child, route[i+1:], item)
   184  	}
   185  
   186  	children := nd.getChildren(route)
   187  	if child, ok := children[route]; ok {
   188  		if child.item != nil {
   189  			return errDupItem
   190  		}
   191  
   192  		child.item = item
   193  	} else {
   194  		children[route] = newNode(item)
   195  	}
   196  
   197  	return nil
   198  }
   199  
   200  func addParam(result *Result, k, v string) {
   201  	if result.Params == nil {
   202  		result.Params = make(map[string]string)
   203  	}
   204  
   205  	result.Params[k] = v
   206  }
   207  
   208  func duplicatedItem(item string) error {
   209  	return fmt.Errorf("duplicated item for %s", item)
   210  }
   211  
   212  func duplicatedSlash(item string) error {
   213  	return fmt.Errorf("duplicated slash for %s", item)
   214  }
   215  
   216  func match(pat, token string) innerResult {
   217  	if pat[0] == colon {
   218  		return innerResult{
   219  			key:   pat[1:],
   220  			value: token,
   221  			named: true,
   222  			found: true,
   223  		}
   224  	}
   225  
   226  	return innerResult{
   227  		found: pat == token,
   228  	}
   229  }
   230  
   231  func newNode(item interface{}) *node {
   232  	return &node{
   233  		item: item,
   234  		children: [2]map[string]*node{
   235  			make(map[string]*node),
   236  			make(map[string]*node),
   237  		},
   238  	}
   239  }