blob: 5d81f5f2585920ed01067d4062f7044d2b4d04bd [file] [log] [blame]
// Copyright 2018 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package graph
import (
"fmt"
"strconv"
"strings"
"go.starlark.net/starlark"
)
// Key is a unique identifier of a node in the graph.
//
// It is constructed from a series of (kind: string, id: string) pairs, and once
// constructed it acts as an opaque label, not examinable through Starlark.
//
// From the Starlark side it looks like a ref-like hashable object: keys can be
// compared to each other via == and !=, and can be used in dicts and sets.
//
// Keys live in a KeySet, which represents a universe of all keys and it is also
// responsible for their construction. Keys from different key sets (even if
// they represent same path) are considered not equal and shouldn't be used
// together.
type Key struct {
set *KeySet // the parent key set that created this key
pairs []string // original list of (kind1, id1, kind2, id2, ...) strings
idx int // index of this key in the KeySet
}
// Last returns the last (kind, id) pair in the key, which usually defines what
// sort of an object this key represents.
func (k *Key) Last() (kind, id string) {
return k.pairs[len(k.pairs)-2], k.pairs[len(k.pairs)-1]
}
// String is part of starlark.Value interface.
//
// Returns [kind1("id1"), kind2("id2"), ...]. Must not be parsed, only for
// logging.
func (k *Key) String() string {
if len(k.pairs)%2 != 0 {
panic("odd") // never happens, validated by KeySet.Key(...) constructor
}
sb := strings.Builder{}
sb.WriteRune('[')
for i := 0; i < len(k.pairs)/2; i++ {
kind, id := k.pairs[i*2], k.pairs[i*2+1]
if i != 0 {
sb.WriteString(", ")
}
sb.WriteString(kind)
sb.WriteRune('(')
sb.WriteString(strconv.Quote(id))
sb.WriteRune(')')
}
sb.WriteRune(']')
return sb.String()
}
// Type is a part of starlark.Value interface.
func (k *Key) Type() string { return "graph.key" }
// Freeze is a part of starlark.Value interface.
func (k *Key) Freeze() {}
// Truth is a part of starlark.Value interface.
func (k *Key) Truth() starlark.Bool { return starlark.True }
// Hash is a part of starlark.Value interface.
func (k *Key) Hash() (uint32, error) { return uint32(k.idx), nil }
// KeySet is a set of all keys ever defined in a graph.
//
// Each key is a singleton object: asking for the same key twice returns exact
// same *Key object. This simplifies comparison of keys and using keys as, well,
// keys in a map.
type KeySet struct {
keys map[string]*Key // compact representation of the key path -> *Key
}
// Key returns a *Key given a list of (kind, id) pairs.
//
// Assumes strings don't have zero bytes. No other restrictions.
func (k *KeySet) Key(pairs ...string) (*Key, error) {
switch {
case len(pairs) == 0:
return nil, fmt.Errorf("empty key path")
case len(pairs)%2 != 0:
return nil, fmt.Errorf("key path %q has odd number of components", pairs)
}
for _, s := range pairs {
if strings.IndexByte(s, 0) != -1 {
return nil, fmt.Errorf("bad key path element %q, has zero byte inside", s)
}
}
keyID := strings.Join(pairs, "\x00")
if key := k.keys[keyID]; key != nil {
return key, nil
}
if k.keys == nil {
k.keys = make(map[string]*Key, 1)
}
key := &Key{set: k, pairs: pairs, idx: len(k.keys)}
k.keys[keyID] = key
return key, nil
}