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
243
244
245
246
247
248
249
250
251
252
253
|
package policy_engine
import (
"regexp"
"strings"
"sync"
"github.com/seaweedfs/seaweedfs/weed/glog"
)
// WildcardMatcher provides unified wildcard matching functionality
type WildcardMatcher struct {
// Use regex for complex patterns with ? wildcards
// Use string manipulation for simple * patterns (better performance)
useRegex bool
regex *regexp.Regexp
pattern string
}
// WildcardMatcherCache provides caching for WildcardMatcher instances
type WildcardMatcherCache struct {
mu sync.RWMutex
matchers map[string]*WildcardMatcher
maxSize int
accessOrder []string // For LRU eviction
}
// NewWildcardMatcherCache creates a new WildcardMatcherCache with a configurable maxSize
func NewWildcardMatcherCache(maxSize int) *WildcardMatcherCache {
if maxSize <= 0 {
maxSize = 1000 // Default value
}
return &WildcardMatcherCache{
matchers: make(map[string]*WildcardMatcher),
maxSize: maxSize,
}
}
// Global cache instance
var wildcardMatcherCache = NewWildcardMatcherCache(1000) // Default maxSize
// GetCachedWildcardMatcher gets or creates a cached WildcardMatcher for the given pattern
func GetCachedWildcardMatcher(pattern string) (*WildcardMatcher, error) {
// Fast path: check if already in cache
wildcardMatcherCache.mu.RLock()
if matcher, exists := wildcardMatcherCache.matchers[pattern]; exists {
wildcardMatcherCache.mu.RUnlock()
wildcardMatcherCache.updateAccessOrder(pattern)
return matcher, nil
}
wildcardMatcherCache.mu.RUnlock()
// Slow path: create new matcher and cache it
wildcardMatcherCache.mu.Lock()
defer wildcardMatcherCache.mu.Unlock()
// Double-check after acquiring write lock
if matcher, exists := wildcardMatcherCache.matchers[pattern]; exists {
wildcardMatcherCache.updateAccessOrderLocked(pattern)
return matcher, nil
}
// Create new matcher
matcher, err := NewWildcardMatcher(pattern)
if err != nil {
return nil, err
}
// Evict old entries if cache is full
if len(wildcardMatcherCache.matchers) >= wildcardMatcherCache.maxSize {
wildcardMatcherCache.evictLeastRecentlyUsed()
}
// Cache it
wildcardMatcherCache.matchers[pattern] = matcher
wildcardMatcherCache.accessOrder = append(wildcardMatcherCache.accessOrder, pattern)
return matcher, nil
}
// updateAccessOrder updates the access order for LRU eviction (with read lock)
func (c *WildcardMatcherCache) updateAccessOrder(pattern string) {
c.mu.Lock()
defer c.mu.Unlock()
c.updateAccessOrderLocked(pattern)
}
// updateAccessOrderLocked updates the access order for LRU eviction (without locking)
func (c *WildcardMatcherCache) updateAccessOrderLocked(pattern string) {
// Remove pattern from its current position
for i, p := range c.accessOrder {
if p == pattern {
c.accessOrder = append(c.accessOrder[:i], c.accessOrder[i+1:]...)
break
}
}
// Add pattern to the end (most recently used)
c.accessOrder = append(c.accessOrder, pattern)
}
// evictLeastRecentlyUsed removes the least recently used pattern from the cache
func (c *WildcardMatcherCache) evictLeastRecentlyUsed() {
if len(c.accessOrder) == 0 {
return
}
// Remove the least recently used pattern (first in the list)
lruPattern := c.accessOrder[0]
c.accessOrder = c.accessOrder[1:]
delete(c.matchers, lruPattern)
}
// ClearCache clears all cached patterns (useful for testing)
func (c *WildcardMatcherCache) ClearCache() {
c.mu.Lock()
defer c.mu.Unlock()
c.matchers = make(map[string]*WildcardMatcher)
c.accessOrder = c.accessOrder[:0]
}
// GetCacheStats returns cache statistics
func (c *WildcardMatcherCache) GetCacheStats() (size int, maxSize int) {
c.mu.RLock()
defer c.mu.RUnlock()
return len(c.matchers), c.maxSize
}
// NewWildcardMatcher creates a new wildcard matcher for the given pattern
func NewWildcardMatcher(pattern string) (*WildcardMatcher, error) {
matcher := &WildcardMatcher{
pattern: pattern,
}
// Determine if we need regex (contains ? wildcards)
if strings.Contains(pattern, "?") {
matcher.useRegex = true
regex, err := compileWildcardPattern(pattern)
if err != nil {
return nil, err
}
matcher.regex = regex
} else {
matcher.useRegex = false
}
return matcher, nil
}
// Match checks if a string matches the wildcard pattern
func (m *WildcardMatcher) Match(str string) bool {
if m.useRegex {
return m.regex.MatchString(str)
}
return matchWildcardString(m.pattern, str)
}
// MatchesWildcard provides a simple function interface for wildcard matching
// This function consolidates the logic from the previous separate implementations
func MatchesWildcard(pattern, str string) bool {
// Handle simple cases first
if pattern == "*" {
return true
}
if pattern == str {
return true
}
// Use regex for patterns with ? wildcards, string manipulation for * only
if strings.Contains(pattern, "?") {
return matchWildcardRegex(pattern, str)
}
return matchWildcardString(pattern, str)
}
// CompileWildcardPattern converts a wildcard pattern to a compiled regex
// This replaces the previous compilePattern function
func CompileWildcardPattern(pattern string) (*regexp.Regexp, error) {
return compileWildcardPattern(pattern)
}
// matchWildcardString uses string manipulation for * wildcards only (more efficient)
func matchWildcardString(pattern, str string) bool {
// Handle simple cases
if pattern == "*" {
return true
}
if pattern == str {
return true
}
// Split pattern by wildcards
parts := strings.Split(pattern, "*")
if len(parts) == 1 {
// No wildcards, exact match
return pattern == str
}
// Check if string starts with first part
if len(parts[0]) > 0 && !strings.HasPrefix(str, parts[0]) {
return false
}
// Check if string ends with last part
if len(parts[len(parts)-1]) > 0 && !strings.HasSuffix(str, parts[len(parts)-1]) {
return false
}
// Check middle parts
searchStr := str
if len(parts[0]) > 0 {
searchStr = searchStr[len(parts[0]):]
}
if len(parts[len(parts)-1]) > 0 {
searchStr = searchStr[:len(searchStr)-len(parts[len(parts)-1])]
}
for i := 1; i < len(parts)-1; i++ {
if len(parts[i]) > 0 {
index := strings.Index(searchStr, parts[i])
if index == -1 {
return false
}
searchStr = searchStr[index+len(parts[i]):]
}
}
return true
}
// matchWildcardRegex uses WildcardMatcher for patterns with ? wildcards
func matchWildcardRegex(pattern, str string) bool {
matcher, err := GetCachedWildcardMatcher(pattern)
if err != nil {
glog.Errorf("Error getting WildcardMatcher for pattern %s: %v. Falling back to matchWildcardString.", pattern, err)
// Fallback to matchWildcardString
return matchWildcardString(pattern, str)
}
return matcher.Match(str)
}
// compileWildcardPattern converts a wildcard pattern to regex
func compileWildcardPattern(pattern string) (*regexp.Regexp, error) {
// Escape special regex characters except * and ?
escaped := regexp.QuoteMeta(pattern)
// Replace escaped wildcards with regex equivalents
escaped = strings.ReplaceAll(escaped, `\*`, `.*`)
escaped = strings.ReplaceAll(escaped, `\?`, `.`)
// Anchor the pattern
escaped = "^" + escaped + "$"
return regexp.Compile(escaped)
}
|