mirror of
https://github.com/anchore/syft.git
synced 2026-10-30 22:12:28 +01:00
309 lines
6.6 KiB
Go
309 lines
6.6 KiB
Go
// Package index provides a concurrent key-split (radix) index, and a forward/reverse pair over it
|
|
// that answers prefix and suffix lookups such as "which names end in .jar".
|
|
package index
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"strings"
|
|
)
|
|
|
|
// ----------------------- KeySplitIndex -----------------------
|
|
|
|
type KeySplitIndex[T any] struct {
|
|
Node[T]
|
|
}
|
|
|
|
var _ interface {
|
|
json.Marshaler
|
|
json.Unmarshaler
|
|
} = (*KeySplitIndex[int])(nil)
|
|
|
|
func (n *KeySplitIndex[T]) MarshalJSON() ([]byte, error) {
|
|
values := map[string]T{}
|
|
collectValues(values, "", &n.Node)
|
|
buf := &bytes.Buffer{}
|
|
enc := json.NewEncoder(buf)
|
|
enc.SetEscapeHTML(false)
|
|
err := enc.Encode(values)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
func (n *KeySplitIndex[T]) UnmarshalJSON(bytes []byte) error {
|
|
values := map[string]T{}
|
|
err := json.Unmarshal(bytes, &values)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for k, v := range values {
|
|
n.Set(k, v)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func collectValues[T any](values map[string]T, key string, node *Node[T]) {
|
|
if node.set {
|
|
values[key] = node.value
|
|
}
|
|
for segment, child := range node.keyMap {
|
|
collectValues(values, key+segment, child)
|
|
}
|
|
}
|
|
|
|
// ----------------------- Node -----------------------
|
|
|
|
type NodeMap[T any] map[string]*Node[T]
|
|
|
|
type Node[T any] struct {
|
|
lockable
|
|
set bool
|
|
value T
|
|
keyMap NodeMap[T]
|
|
keyByChar map[rune]string
|
|
}
|
|
|
|
type NodeUpdateFunc[T any] func(current *Node[T]) (newValue T)
|
|
|
|
func (n *Node[T]) Value() (v T) {
|
|
unlock := n.RLock()
|
|
v = n.value
|
|
unlock()
|
|
return
|
|
}
|
|
|
|
func (n *Node[T]) Get(s string) (out T) {
|
|
v, equal := n._find(s)
|
|
if !equal || v == nil {
|
|
return
|
|
}
|
|
out = v.value
|
|
return
|
|
}
|
|
|
|
func (n *Node[T]) Set(name string, value T) {
|
|
node := n._makeNodeP(nil, name, nil)
|
|
node.SetValue(value)
|
|
}
|
|
|
|
func (n *Node[T]) ByPrefix(s string) []T {
|
|
v, _ := n._find(s)
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
return v.Collect()
|
|
}
|
|
|
|
func (n *Node[T]) Update(name string, f NodeUpdateFunc[T]) {
|
|
unlock := n.Lock()
|
|
node := n._makeNodeP(&unlock, name, nil)
|
|
v := f(node)
|
|
node.SetValue(v)
|
|
unlock()
|
|
}
|
|
|
|
func (n *Node[T]) SetValue(value T) {
|
|
unlock := n.Lock()
|
|
n.value = value
|
|
n.set = true
|
|
unlock()
|
|
}
|
|
|
|
func (n *Node[T]) Collect() (values []T) {
|
|
n._collect(&values)
|
|
return values
|
|
}
|
|
|
|
func (n *Node[T]) _collect(values *[]T) {
|
|
unlock := n.RLock()
|
|
if n.set {
|
|
*values = append(*values, n.value)
|
|
}
|
|
|
|
for _, v := range n.keyMap {
|
|
v._collect(values)
|
|
}
|
|
|
|
unlock()
|
|
}
|
|
|
|
// _find returns the node for s and whether it matched exactly.
|
|
func (n *Node[T]) _find(s string) (node *Node[T], equal bool) {
|
|
defer n.RLock()()
|
|
|
|
if s == "" {
|
|
return n, true
|
|
}
|
|
|
|
ch := rune(s[0])
|
|
key := n.keyByChar[ch]
|
|
if key == "" {
|
|
return nil, false
|
|
}
|
|
|
|
offset := len(key)
|
|
|
|
// check our part of the key matches
|
|
if offset > 1 {
|
|
for i := 1; i < len(s) && i < offset; i++ {
|
|
if s[i] != key[i] {
|
|
return nil, false
|
|
}
|
|
}
|
|
}
|
|
|
|
next := n.keyMap[key]
|
|
|
|
// our key matched, it's equal -- just return the node
|
|
if offset == len(s) {
|
|
return next, true
|
|
}
|
|
|
|
// our key matched, it's longer -- just return the node as a non-equality match
|
|
if offset >= len(s) {
|
|
return next, false
|
|
}
|
|
|
|
// our key matched, but it's shorter -- return what we find for the next portion
|
|
return next._find(s[offset:])
|
|
}
|
|
|
|
//nolint:funlen,gocognit
|
|
func (n *Node[T]) _makeNodeP(unlock *func(), name string, nodeIfEmpty *Node[T]) *Node[T] {
|
|
if name == "" {
|
|
return n
|
|
}
|
|
|
|
if unlock == nil {
|
|
rUnlock := n.RLock()
|
|
n = n._makeNodeP(&rUnlock, name, nodeIfEmpty)
|
|
rUnlock()
|
|
return n
|
|
}
|
|
|
|
ch := rune(name[0])
|
|
|
|
if n.keyByChar == nil || n.keyMap == nil {
|
|
n._exclusiveLock(unlock)
|
|
|
|
if n.keyByChar == nil {
|
|
n.keyByChar = map[rune]string{}
|
|
}
|
|
if n.keyMap == nil {
|
|
n.keyMap = NodeMap[T]{}
|
|
}
|
|
}
|
|
|
|
key, ok := n.keyByChar[ch]
|
|
|
|
// no entry for the given character
|
|
if !ok {
|
|
// check again with an exclusive lock before creating a new entry
|
|
n._exclusiveLock(unlock)
|
|
|
|
key, ok = n.keyByChar[ch]
|
|
if !ok {
|
|
// no entry for the given character, create one; this is all we have to do
|
|
newNode := nodeIfEmpty
|
|
if newNode == nil {
|
|
newNode = &Node[T]{}
|
|
}
|
|
n.keyMap[name] = newNode
|
|
n.keyByChar[ch] = name
|
|
|
|
return newNode
|
|
}
|
|
}
|
|
|
|
switch {
|
|
case key == name:
|
|
existingNode := n.keyMap[key]
|
|
if existingNode == nil {
|
|
// try this again, something changed between read checks and exclusive lock
|
|
return n._makeNodeP(unlock, name, nodeIfEmpty)
|
|
}
|
|
|
|
return existingNode
|
|
case strings.HasPrefix(key, name):
|
|
// existing key is longer than my key, we can just use the existing and make a new sub-entry for the longer key
|
|
n._exclusiveLock(unlock)
|
|
|
|
existingNode := n.keyMap[key]
|
|
if existingNode == nil {
|
|
// try this again, something changed between read checks and exclusive lock
|
|
return n._makeNodeP(unlock, name, nodeIfEmpty)
|
|
}
|
|
delete(n.keyMap, key)
|
|
|
|
newNode := nodeIfEmpty
|
|
if newNode == nil {
|
|
newNode = &Node[T]{}
|
|
}
|
|
n.keyMap[name] = newNode
|
|
n.keyByChar[ch] = name
|
|
|
|
next := key[len(name):]
|
|
_ = newNode._makeNodeP(nil, next, existingNode)
|
|
|
|
return newNode
|
|
case strings.HasPrefix(name, key):
|
|
// existing key is shorter than my key, we can just take the substring to remove the
|
|
// existing string prefix and set the new node as a child of the existing node
|
|
n._exclusiveLock(unlock)
|
|
|
|
existingNode := n.keyMap[key]
|
|
if existingNode == nil {
|
|
// try this again, something changed between read checks and exclusive lock
|
|
return n._makeNodeP(unlock, name, nodeIfEmpty)
|
|
}
|
|
|
|
next := name[len(key):]
|
|
return existingNode._makeNodeP(nil, next, nodeIfEmpty)
|
|
default:
|
|
// neither the existing key nor my key contains a prefix of the other, so we find
|
|
// the longest common prefix and split BOTH entries as children of a new entry with this common prefix
|
|
n._exclusiveLock(unlock)
|
|
|
|
commonLength := 1 // the first character already matches
|
|
minLength := len(key)
|
|
if len(name) < minLength {
|
|
minLength = len(name)
|
|
}
|
|
for ; commonLength < minLength; commonLength++ {
|
|
if key[commonLength] != name[commonLength] {
|
|
break
|
|
}
|
|
}
|
|
|
|
existingNode := n.keyMap[key]
|
|
if existingNode == nil {
|
|
// try this again, something changed between read checks and exclusive lock
|
|
return n._makeNodeP(unlock, name, nodeIfEmpty)
|
|
}
|
|
delete(n.keyMap, key)
|
|
|
|
newNode := nodeIfEmpty
|
|
if newNode == nil {
|
|
newNode = &Node[T]{}
|
|
}
|
|
parentNode := &Node[T]{}
|
|
parentNode._makeNodeP(nil, key[commonLength:], existingNode)
|
|
parentNode._makeNodeP(nil, name[commonLength:], newNode)
|
|
|
|
common := key[:commonLength]
|
|
n.keyMap[common] = parentNode
|
|
n.keyByChar[ch] = common
|
|
|
|
return newNode
|
|
}
|
|
}
|
|
|
|
func (n *Node[T]) _exclusiveLock(unlocker *func()) {
|
|
if !n.isExclusiveLock(*unlocker) {
|
|
(*unlocker)()
|
|
*unlocker = n.Lock()
|
|
}
|
|
}
|