-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathsegments_tree.go
157 lines (131 loc) · 4.05 KB
/
segments_tree.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
package quamina
import (
"fmt"
"strings"
)
const SegmentSeparator = "\n"
// segmentsTree implements the SegmentsTreeTracker interface, and includes other calls used by
// the AddPattern() code to load up the tree tracker.
type segmentsTree struct {
root bool
// nodes stores a map from a segment to its children.
// in a hierarchial data format like JSON, a node can be Object or Array.
// for example, in this path "context\nuser\nid", both "context" and "user" will be nodes.
nodes map[string]*segmentsTree
// fields maps the children of this node which are leafs rather than nodes
// to the []byte representation of the Path component of the Field.
// In the "context\nuser\nid" example:
// leaf "id" will be mapped to []byte("context\nuser\nid")
// leaf "user", if it has non-node values, will be mapped to []byte("context\nuser")
fields map[string][]byte
}
// newSegmentsIndex creates a segmentsTree node which is the root.
// The paths argument is used for testing; it auto-adds those to the tree.
func newSegmentsIndex(paths ...string) *segmentsTree {
st := newSegmentsIndexNode(true)
for _, path := range paths {
st.add(path)
}
return st
}
// newSegmentsIndexNode initializes a segmentsTree node
func newSegmentsIndexNode(root bool) *segmentsTree {
return &segmentsTree{
root: root,
nodes: make(map[string]*segmentsTree),
fields: make(map[string][]byte),
}
}
func (p *segmentsTree) add(path string) {
segments := strings.Split(path, SegmentSeparator)
// If we have only one segment, it's a field on the root.
if len(segments) == 1 {
// It's a direct field.
p.fields[path] = []byte(path)
return
}
var node *segmentsTree
node = p
for i, segment := range segments {
// If this the last segment, add it as field
// example: context\nuser\nid, in this case "id" is the field ("context" & "user" are nodes)
if i == len(segments)-1 {
node.addSegment(segment, []byte(path))
} else {
node = node.getOrCreate(segment)
}
}
}
func (p *segmentsTree) getOrCreate(name string) *segmentsTree {
_, ok := p.nodes[name]
if !ok {
p.nodes[name] = newSegmentsIndexNode(false)
}
return p.nodes[name]
}
func (p *segmentsTree) addSegment(segment string, path []byte) {
_, ok := p.fields[segment]
if !ok {
p.fields[segment] = path
}
}
// Get implements SegmentsTreeTracker
func (p *segmentsTree) Get(name []byte) (SegmentsTreeTracker, bool) {
n, ok := p.nodes[string(name)]
return n, ok
}
// IsRoot implements SegmentsTreeTracker
func (p *segmentsTree) IsRoot() bool {
return p.root
}
// IsSegmentUsed implements SegmentsTreeTracker
func (p *segmentsTree) IsSegmentUsed(segment []byte) bool {
// In the next path: "context\nuser\nid"
// "context" / "user" are nodes, while "id" is a field
// As a result a segment can be both node and field, we need to check
// in both maps.
_, isField := p.fields[string(segment)]
if isField {
return true
}
_, isNode := p.nodes[string(segment)]
return isNode
}
// PathForSegment implements SegmentsTreeTracker
func (p *segmentsTree) PathForSegment(segment []byte) []byte {
return p.fields[string(segment)]
}
// NodesCount implements SegmentsTreeTracker
func (p *segmentsTree) NodesCount() int {
return len(p.nodes)
}
// FieldsCount implements SegmentsTreeTracker
func (p *segmentsTree) FieldsCount() int {
return len(p.fields)
}
// String used for debugging purposes
func (p *segmentsTree) String() string {
nodeNames := make([]string, 0)
for n := range p.nodes {
nodeNames = append(nodeNames, n)
}
fieldNames := make([]string, 0)
for f := range p.fields {
fieldNames = append(fieldNames, f)
}
return fmt.Sprintf("root: %v, nodes [%s], fields: [%s]", p.root, strings.Join(nodeNames, ","), strings.Join(fieldNames, ","))
}
// copy produces a fresh copy of an existing segmentsTree which is used to support atomic update of
// the Quamina automaton.
func (p *segmentsTree) copy() *segmentsTree {
np := newSegmentsIndexNode(p.root)
// copy fields
for name, path := range p.fields {
np.fields[name] = path
}
// copy nodes
for name, node := range p.nodes {
np.nodes[name] = node.copy()
}
return np
}