-
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathenumcache.go
More file actions
242 lines (193 loc) · 5.87 KB
/
Copy pathenumcache.go
File metadata and controls
242 lines (193 loc) · 5.87 KB
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/*
Package enumcache provides thread-safe storage and lookup for enumeration name
and ID mappings.
It maps string names to integer IDs and back, and supports encoding and decoding
enum bitmaps when enum IDs represent bit flags.
Typical usage is set-once, read-many: populate entries during startup using Set,
SetAllIDByName, or SetAllNameByID, then perform lookups from application code.
*/
package enumcache
import (
"errors"
"fmt"
"sort"
"sync"
"github.com/tecnickcom/nurago/pkg/enumbitmap"
)
var (
// ErrNameNotFound is returned by ID when the requested name is not cached.
// Match it with errors.Is.
ErrNameNotFound = errors.New("enumcache: name not found")
// ErrIDNotFound is returned by Name when the requested id is not cached.
// Match it with errors.Is.
ErrIDNotFound = errors.New("enumcache: ID not found")
)
// IDByName maps enum names to numeric IDs.
type IDByName map[string]int
// NameByID maps integers to string names.
type NameByID map[int]string
// EnumCache stores bidirectional enum mappings (name<->ID).
type EnumCache struct {
mu sync.RWMutex
id IDByName
name NameByID
}
// New creates an empty thread-safe enum cache.
//
// The cache supports bidirectional name/id lookups and bitmask conversions.
func New() *EnumCache {
return &EnumCache{
id: make(IDByName),
name: make(NameByID),
}
}
// Set stores a single enum mapping pair.
//
// Existing values for id or name are overwritten. When the id or name was
// previously associated with a different counterpart, the stale reverse mapping
// is removed so both directions stay consistent.
func (ec *EnumCache) Set(id int, name string) {
ec.mu.Lock()
defer ec.mu.Unlock()
ec.set(id, name)
}
// Delete removes the mapping for id together with its associated name.
//
// It is a no-op when id is not present. Both internal maps are kept consistent.
func (ec *EnumCache) Delete(id int) {
ec.mu.Lock()
defer ec.mu.Unlock()
name, ok := ec.name[id]
if !ok {
return
}
delete(ec.name, id)
delete(ec.id, name)
}
// SetAllIDByName bulk-loads enum values from name-to-id input.
//
// It is useful when parsing static definitions keyed by symbolic names.
func (ec *EnumCache) SetAllIDByName(enum IDByName) {
ec.mu.Lock()
defer ec.mu.Unlock()
for name, id := range enum {
ec.set(id, name)
}
}
// SetAllNameByID bulk-loads enum values from id-to-name input.
//
// It is useful when loading rows from storage keyed by numeric IDs.
func (ec *EnumCache) SetAllNameByID(enum NameByID) {
ec.mu.Lock()
defer ec.mu.Unlock()
for id, name := range enum {
ec.set(id, name)
}
}
// ID returns the numeric ID associated with name.
//
// It returns an error wrapping ErrNameNotFound when name is not present.
func (ec *EnumCache) ID(name string) (int, error) {
ec.mu.RLock()
defer ec.mu.RUnlock()
id, ok := ec.id[name]
if !ok {
return 0, fmt.Errorf("%w: %s", ErrNameNotFound, name)
}
return id, nil
}
// Name returns the symbolic name associated with id.
//
// It returns an error wrapping ErrIDNotFound when id is not present.
func (ec *EnumCache) Name(id int) (string, error) {
ec.mu.RLock()
defer ec.mu.RUnlock()
name, ok := ec.name[id]
if !ok {
return "", fmt.Errorf("%w: %d", ErrIDNotFound, id)
}
return name, nil
}
// SortNames returns all cached names in ascending lexical order.
//
// This is useful for deterministic output and tests.
func (ec *EnumCache) SortNames() []string {
ec.mu.RLock()
defer ec.mu.RUnlock()
sorted := make([]string, 0, len(ec.id))
for name := range ec.id {
sorted = append(sorted, name)
}
sort.Strings(sorted)
return sorted
}
// SortIDs returns all cached IDs in ascending numeric order.
//
// This is useful for deterministic output and tests.
func (ec *EnumCache) SortIDs() []int {
ec.mu.RLock()
defer ec.mu.RUnlock()
sorted := make([]int, 0, len(ec.name))
for id := range ec.name {
sorted = append(sorted, id)
}
sort.Ints(sorted)
return sorted
}
// Len returns the number of cached enum pairs.
func (ec *EnumCache) Len() int {
ec.mu.RLock()
defer ec.mu.RUnlock()
return len(ec.id)
}
// Has reports whether name is present in the cache.
func (ec *EnumCache) Has(name string) bool {
ec.mu.RLock()
defer ec.mu.RUnlock()
_, ok := ec.id[name]
return ok
}
// HasID reports whether id is present in the cache.
func (ec *EnumCache) HasID(id int) bool {
ec.mu.RLock()
defer ec.mu.RUnlock()
_, ok := ec.name[id]
return ok
}
// DecodeBinaryMap expands a bitmask value into enum names.
//
// The cache must contain bit-value IDs (single-bit powers of two, 1<<0 through
// 1<<31) mapped to names; IDs that are 0 or multi-bit cannot be decoded and are
// silently unreachable. On unknown set bits the returned error wraps
// enumbitmap.ErrUnknownBitValues while known names are still returned.
func (ec *EnumCache) DecodeBinaryMap(v int) ([]string, error) {
ec.mu.RLock()
defer ec.mu.RUnlock()
return enumbitmap.BitMapToStrings(ec.name, v) //nolint:wrapcheck
}
// EncodeBinaryMap combines enum names into a bitmask value.
//
// The cache must contain bit-value IDs (single-bit powers of two, 1<<0 through
// 1<<31) mapped to names for the result to round-trip through DecodeBinaryMap. On
// unknown names the returned error wraps enumbitmap.ErrUnknownStringValues while
// known names are still combined into the bitmask.
func (ec *EnumCache) EncodeBinaryMap(s []string) (int, error) {
ec.mu.RLock()
defer ec.mu.RUnlock()
return enumbitmap.StringsToBitMap(ec.id, s) //nolint:wrapcheck
}
// set stores a single id<->name pair, keeping both internal maps consistent.
//
// When the id or the name already maps to a different counterpart, the stale
// reverse-map entry is removed so the two maps never desync. The caller must
// hold the write lock.
func (ec *EnumCache) set(id int, name string) {
if oldName, ok := ec.name[id]; ok && oldName != name {
delete(ec.id, oldName)
}
if oldID, ok := ec.id[name]; ok && oldID != id {
delete(ec.name, oldID)
}
ec.name[id] = name
ec.id[name] = id
}