1
0
mirror of https://github.com/strongdm/comply synced 2024-07-08 17:31:47 +00:00
comply/vendor/github.com/trivago/tgo/tcontainer/trie.go
Justin McCarthy 7468711b3b
go.mod: update gitlab dep to v0.30.1
Releases after github.com/xanzy/go-gitlab@v0.31.0 introduce breaking
changes to the NewClient call.

Addresses #94
2020-09-14 11:31:56 -07:00

228 lines
6.0 KiB
Go

// Copyright 2015-2018 trivago N.V.
//
// 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 tcontainer
// TrieNode represents a single node inside a trie.
// Each node can contain a payload which can be retrieved after a successfull
// match. In addition to that PathLen will contain the length of the match.
type TrieNode struct {
suffix []byte
children []*TrieNode
longestPath int
PathLen int
Payload interface{}
}
// NewTrie creates a new root TrieNode
func NewTrie(data []byte, payload interface{}) *TrieNode {
return &TrieNode{
suffix: data,
children: []*TrieNode{},
longestPath: len(data),
PathLen: len(data),
Payload: payload,
}
}
func (node *TrieNode) addNewChild(data []byte, payload interface{}, pathLen int) {
if node.longestPath < pathLen {
node.longestPath = pathLen
}
idx := len(node.children)
node.children = append(node.children, nil)
for idx > 0 {
nextIdx := idx - 1
if node.children[nextIdx].longestPath > pathLen {
break
}
node.children[idx] = node.children[nextIdx]
idx = nextIdx
}
node.children[idx] = &TrieNode{
suffix: data,
children: []*TrieNode{},
longestPath: pathLen,
PathLen: pathLen,
Payload: payload,
}
}
func (node *TrieNode) replace(oldChild *TrieNode, newChild *TrieNode) {
for i, child := range node.children {
if child == oldChild {
node.children[i] = newChild
return // ### return, replaced ###
}
}
}
// ForEach applies a function to each node in the tree including and below the
// passed node.
func (node *TrieNode) ForEach(callback func(*TrieNode)) {
callback(node)
for _, child := range node.children {
child.ForEach(callback)
}
}
// Add adds a new data path to the trie.
// The TrieNode returned is the (new) root node so you should always reassign
// the root with the return value of Add.
func (node *TrieNode) Add(data []byte, payload interface{}) *TrieNode {
return node.addPath(data, payload, len(data), nil)
}
func (node *TrieNode) addPath(data []byte, payload interface{}, pathLen int, parent *TrieNode) *TrieNode {
dataLen := len(data)
suffixLen := len(node.suffix)
testLen := suffixLen
if dataLen < suffixLen {
testLen = dataLen
}
var splitIdx int
for splitIdx = 0; splitIdx < testLen; splitIdx++ {
if data[splitIdx] != node.suffix[splitIdx] {
break // ### break, split found ###
}
}
if splitIdx == suffixLen {
// Continue down or stop here (full suffix match)
if splitIdx == dataLen {
node.Payload = payload // may overwrite
return node // ### return, path already stored ###
}
data = data[splitIdx:]
if suffixLen > 0 {
for _, child := range node.children {
if child.suffix[0] == data[0] {
child.addPath(data, payload, pathLen, node)
return node // ### return, continue on path ###
}
}
}
node.addNewChild(data, payload, pathLen)
return node // ### return, new leaf ###
}
if splitIdx == dataLen {
// Make current node a subpath of new data node (full data match)
// This case implies that dataLen < suffixLen as splitIdx == suffixLen
// did not match.
node.suffix = node.suffix[splitIdx:]
newParent := NewTrie(data, payload)
newParent.PathLen = pathLen
newParent.longestPath = node.longestPath
newParent.children = []*TrieNode{node}
if parent != nil {
parent.replace(node, newParent)
}
return newParent // ### return, rotation ###
}
// New parent required with both nodes as children (partial match)
node.suffix = node.suffix[splitIdx:]
newParent := NewTrie(data[:splitIdx], nil)
newParent.PathLen = 0
newParent.longestPath = node.longestPath
newParent.children = []*TrieNode{node}
newParent.addNewChild(data[splitIdx:], payload, pathLen)
if parent != nil {
parent.replace(node, newParent)
}
return newParent // ### return, new parent ###
}
// Match compares the trie to the given data stream.
// Match returns true if data can be completely matched to the trie.
func (node *TrieNode) Match(data []byte) *TrieNode {
dataLen := len(data)
suffixLen := len(node.suffix)
if dataLen < suffixLen {
return nil // ### return, cannot be fully matched ###
}
for i := 0; i < suffixLen; i++ {
if data[i] != node.suffix[i] {
return nil // ### return, no match ###
}
}
if dataLen == suffixLen {
if node.PathLen > 0 {
return node // ### return, full match ###
}
return nil // ### return, invalid match ###
}
data = data[suffixLen:]
numChildren := len(node.children)
for i := 0; i < numChildren; i++ {
matchedNode := node.children[i].Match(data)
if matchedNode != nil {
return matchedNode // ### return, match found ###
}
}
return nil // ### return, no valid path ###
}
// MatchStart compares the trie to the beginning of the given data stream.
// MatchStart returns true if the beginning of data can be matched to the trie.
func (node *TrieNode) MatchStart(data []byte) *TrieNode {
dataLen := len(data)
suffixLen := len(node.suffix)
if dataLen < suffixLen {
return nil // ### return, cannot be fully matched ###
}
for i := 0; i < suffixLen; i++ {
if data[i] != node.suffix[i] {
return nil // ### return, no match ###
}
}
// Match longest path first
data = data[suffixLen:]
numChildren := len(node.children)
for i := 0; i < numChildren; i++ {
matchedNode := node.children[i].MatchStart(data)
if matchedNode != nil {
return matchedNode // ### return, match found ###
}
}
// May be only a part of data but we have a valid match
if node.PathLen > 0 {
return node // ### return, full match ###
}
return nil // ### return, no valid path ###
}