forked from sdboyer/gps
/
typed_radix.go
187 lines (164 loc) · 4.74 KB
/
typed_radix.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package gps
import (
"strings"
"sync"
"github.com/armon/go-radix"
)
// Typed implementations of radix trees. These are just simple wrappers that let
// us avoid having to type assert anywhere else, cleaning up other code a bit.
//
// Some of the more annoying things to implement (like walks) aren't
// implemented. They can be added if/when we actually need them.
//
// Oh generics, where art thou...
type deducerTrie struct {
sync.RWMutex
t *radix.Tree
}
func newDeducerTrie() *deducerTrie {
return &deducerTrie{
t: radix.New(),
}
}
// Delete is used to delete a key, returning the previous value and if it was deleted
func (t *deducerTrie) Delete(s string) (pathDeducer, bool) {
t.Lock()
defer t.Unlock()
if d, had := t.t.Delete(s); had {
return d.(pathDeducer), had
}
return nil, false
}
// Get is used to lookup a specific key, returning the value and if it was found
func (t *deducerTrie) Get(s string) (pathDeducer, bool) {
t.RLock()
defer t.RUnlock()
if d, has := t.t.Get(s); has {
return d.(pathDeducer), has
}
return nil, false
}
// Insert is used to add a newentry or update an existing entry. Returns if updated.
func (t *deducerTrie) Insert(s string, d pathDeducer) (pathDeducer, bool) {
t.Lock()
defer t.Unlock()
if d2, had := t.t.Insert(s, d); had {
return d2.(pathDeducer), had
}
return nil, false
}
// Len is used to return the number of elements in the tree
func (t *deducerTrie) Len() int {
t.RLock()
defer t.RUnlock()
return t.t.Len()
}
// LongestPrefix is like Get, but instead of an exact match, it will return the
// longest prefix match.
func (t *deducerTrie) LongestPrefix(s string) (string, pathDeducer, bool) {
t.RLock()
defer t.RUnlock()
if p, d, has := t.t.LongestPrefix(s); has {
return p, d.(pathDeducer), has
}
return "", nil, false
}
// ToMap is used to walk the tree and convert it to a map.
func (t *deducerTrie) ToMap() map[string]pathDeducer {
m := make(map[string]pathDeducer)
t.RLock()
t.t.Walk(func(s string, d interface{}) bool {
m[s] = d.(pathDeducer)
return false
})
t.RUnlock()
return m
}
type prTrie struct {
sync.RWMutex
t *radix.Tree
}
func newProjectRootTrie() *prTrie {
return &prTrie{
t: radix.New(),
}
}
// Delete is used to delete a key, returning the previous value and if it was deleted
func (t *prTrie) Delete(s string) (ProjectRoot, bool) {
t.Lock()
defer t.Unlock()
if pr, had := t.t.Delete(s); had {
return pr.(ProjectRoot), had
}
return "", false
}
// Get is used to lookup a specific key, returning the value and if it was found
func (t *prTrie) Get(s string) (ProjectRoot, bool) {
t.RLock()
defer t.RUnlock()
if pr, has := t.t.Get(s); has {
return pr.(ProjectRoot), has
}
return "", false
}
// Insert is used to add a newentry or update an existing entry. Returns if updated.
func (t *prTrie) Insert(s string, pr ProjectRoot) (ProjectRoot, bool) {
t.Lock()
defer t.Unlock()
if pr2, had := t.t.Insert(s, pr); had {
return pr2.(ProjectRoot), had
}
return "", false
}
// Len is used to return the number of elements in the tree
func (t *prTrie) Len() int {
t.RLock()
defer t.RUnlock()
return t.t.Len()
}
// LongestPrefix is like Get, but instead of an exact match, it will return the
// longest prefix match.
func (t *prTrie) LongestPrefix(s string) (string, ProjectRoot, bool) {
t.RLock()
defer t.RUnlock()
if p, pr, has := t.t.LongestPrefix(s); has && isPathPrefixOrEqual(p, s) {
return p, pr.(ProjectRoot), has
}
return "", "", false
}
// ToMap is used to walk the tree and convert it to a map.
func (t *prTrie) ToMap() map[string]ProjectRoot {
t.RLock()
m := make(map[string]ProjectRoot)
t.t.Walk(func(s string, pr interface{}) bool {
m[s] = pr.(ProjectRoot)
return false
})
t.RUnlock()
return m
}
// isPathPrefixOrEqual is an additional helper check to ensure that the literal
// string prefix returned from a radix tree prefix match is also a path tree
// match.
//
// The radix tree gets it mostly right, but we have to guard against
// possibilities like this:
//
// github.com/sdboyer/foo
// github.com/sdboyer/foobar/baz
//
// The latter would incorrectly be conflated with the former. As we know we're
// operating on strings that describe import paths, guard against this case by
// verifying that either the input is the same length as the match (in which
// case we know they're equal), or that the next character is a "/". (Import
// paths are defined to always use "/", not the OS-specific path separator.)
func isPathPrefixOrEqual(pre, path string) bool {
prflen, pathlen := len(pre), len(path)
if pathlen == prflen+1 {
// this can never be the case
return false
}
// we assume something else (a trie) has done equality check up to the point
// of the prefix, so we just check len
return prflen == pathlen || strings.Index(path[prflen:], "/") == 0
}