blob: 690300e5c28f4087b697ef61daa6380de1c204ce [file] [log] [blame]
// Package params provides Params and ParamSet.
package paramtools
import (
"bytes"
"encoding/gob"
"sort"
"strconv"
"strings"
"sync"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/util"
)
// Params is a set of key,value pairs.
type Params map[string]string
// ParamSet is a set of keys and the possible values that the keys could have. I.e.
// the []string should contain no duplicates.
type ParamSet map[string][]string
// NewParams returns the parsed structured key (see query) as Params.
//
// It presumes a valid key, i.e. something that passed query.ValidateKey.
func NewParams(key string) Params {
ret := Params{}
parts := strings.Split(key, ",")
parts = parts[1 : len(parts)-1]
for _, s := range parts {
pair := strings.SplitN(s, "=", 2)
ret[pair[0]] = pair[1]
}
return ret
}
// Add adds each set of Params in order to this Params.
//
// Values in p will be overwritten.
func (p Params) Add(b ...Params) {
for _, oneMap := range b {
for k, v := range oneMap {
p[k] = v
}
}
}
// Copy returns a copy of the Params.
func (p Params) Copy() Params {
ret := make(Params, len(p))
for k, v := range p {
ret[k] = v
}
return ret
}
// Equal returns true if this Params equals a.
func (p Params) Equal(a Params) bool {
if len(a) != len(p) {
return false
}
// Since they are the same size we only need to check from one side, i.e.
// compare a's values to p's values.
for k, v := range a {
if bv, ok := p[k]; !ok || bv != v {
return false
}
}
return true
}
// Keys returns the keys of the Params.
func (p Params) Keys() []string {
ret := make([]string, 0, len(p))
for v := range p {
ret = append(ret, v)
}
return ret
}
// NewParamSet returns a new ParamSet initialized with the given maps of parameters.
func NewParamSet(ps ...Params) ParamSet {
ret := ParamSet{}
for _, onePS := range ps {
ret.AddParams(onePS)
}
return ret
}
// Add the Params to this ParamSet.
func (p ParamSet) AddParams(ps Params) {
for k, v := range ps {
// You might be tempted to replace this with
// sort.SearchStrings(), but that's actually slower for short
// slices. The breakpoint seems to around 50, and since most
// of our ParamSet lists are short that ends up being slower.
params := p[k]
if !util.In(v, params) {
p[k] = append(params, v)
}
}
}
// AddParamsFromKey is the same as calling
//
// paramset.AddParams(NewParams(key))
//
// but without creating the intermediate Params.
//
// It presumes a valid key, i.e. something that passed query.ValidateKey.
func (p ParamSet) AddParamsFromKey(key string) {
parts := strings.Split(key, ",")
parts = parts[1 : len(parts)-1]
for _, s := range parts {
pair := strings.SplitN(s, "=", 2)
params := p[pair[0]]
if !util.In(pair[1], params) {
p[pair[0]] = append(params, pair[1])
}
}
}
// Add the ParamSet to this ParamSet.
func (p ParamSet) AddParamSet(ps ParamSet) {
for k, arr := range ps {
if _, ok := p[k]; !ok {
p[k] = append([]string{}, arr...)
} else {
for _, v := range arr {
if !util.In(v, p[k]) {
p[k] = append(p[k], v)
}
}
}
}
}
// Keys returns the keys of the ParamSet.
func (p ParamSet) Keys() []string {
ret := make([]string, 0, len(p))
for v := range p {
ret = append(ret, v)
}
return ret
}
// Copy returns a copy of the ParamSet.
func (p ParamSet) Copy() ParamSet {
ret := ParamSet{}
for k, v := range p {
newV := make([]string, len(v), len(v))
copy(newV, v)
ret[k] = newV
}
return ret
}
// Normalize all the values by sorting them.
func (p ParamSet) Normalize() {
for _, arr := range p {
sort.Strings(arr)
}
}
// Matches returns true if the params in 'p' match the sets given in 'right'.
// For every key in 'p' there has to be a matching key in 'right' and
// the intersection of their values must be not empty.
func (p ParamSet) Matches(right ParamSet) bool {
for key, vals := range p {
rightVals, ok := right[key]
if !ok {
return false
}
found := false
for _, targetVal := range vals {
if util.In(targetVal, rightVals) {
found = true
break
}
}
if !found {
return false
}
}
return true
}
// MatchesParams returns true if the params in 'p' match the values given in 'right'.
// For every key in 'p' there has to be a matching key in 'right' and
// the intersection of their values must be not empty.
func (p ParamSet) MatchesParams(right Params) bool {
for key, vals := range p {
rightVal, ok := right[key]
if !ok {
return false
}
found := false
for _, targetVal := range vals {
if targetVal == rightVal {
found = true
break
}
}
if !found {
return false
}
}
return true
}
// Size returns the total number of values in the ParamSet.
func (p ParamSet) Size() int {
var ret int
for _, vals := range p {
ret += len(vals)
}
return ret
}
// ParamMatcher is a list of Paramsets that can be matched against. The primary
// purpose is to match against a set of rules, e.g. ignore rules.
type ParamMatcher []ParamSet
// MatchAny returns true if the given ParamSet matches any of the rules in the matcher.
func (p ParamMatcher) MatchAny(params ParamSet) bool {
for _, oneRule := range p {
if oneRule.Matches(params) {
return true
}
}
return false
}
// MatchAnyParams returns true if the given Params matches any of the rules in the matcher.
func (p ParamMatcher) MatchAnyParams(params Params) bool {
for _, oneRule := range p {
if oneRule.MatchesParams(params) {
return true
}
}
return false
}
// OrderedParamSet is a ParamSet that keeps track of the order in which
// keys and values were added, which allows for compressing Params
// that make up the ParamSet to a smaller number of bytes.
//
// For example, if the paramset has just one key and two values:
//
// "config": ["8888", "565"]
//
// Then a Params of
//
// "config": "565"
//
// can be represented as two integers, the offset of "config" into the list of
// ordered keys (0), and the offset of "565" into the list of all values seen
// for that key (1).
//
// As a structured key that could be represented as:
//
// ,0=1,
//
// Which is a savings over
//
// ,config=565,
//
// OrderedParamSet is not Go routine safe.
type OrderedParamSet struct {
mutex sync.Mutex
KeyOrder []string
ParamSet ParamSet
paramsEncoder *paramsEncoder
paramsDecoder *paramsDecoder
}
func NewOrderedParamSet() *OrderedParamSet {
return &OrderedParamSet{
KeyOrder: []string{},
ParamSet: ParamSet{},
paramsEncoder: nil,
paramsDecoder: nil,
}
}
func (o *OrderedParamSet) Copy() *OrderedParamSet {
ret := &OrderedParamSet{
KeyOrder: make([]string, len(o.KeyOrder)),
ParamSet: o.ParamSet.Copy(),
}
copy(ret.KeyOrder, o.KeyOrder)
return ret
}
// Delta returns all the keys and their values that don't exist in the
// OrderedParamSet.
func (o *OrderedParamSet) Delta(p ParamSet) ParamSet {
ret := ParamSet{}
for k, newValues := range p {
if values, ok := o.ParamSet[k]; !ok {
ret[k] = newValues
} else {
for _, v := range newValues {
if !util.In(v, values) {
ret[k] = append(ret[k], v)
}
}
}
}
return ret
}
// Update adds all the key/value pairs from ParamSet, which is usually the
// ParamSet returned from Delta().
func (o *OrderedParamSet) Update(p ParamSet) {
o.mutex.Lock()
defer o.mutex.Unlock()
o.paramsEncoder = nil
o.paramsDecoder = nil
// Add new keys to KeyOrder.
// Append new values if they don't exist.
for k, values := range p {
currentValues, ok := o.ParamSet[k]
if !ok {
o.KeyOrder = append(o.KeyOrder, k)
o.ParamSet[k] = []string{}
currentValues = []string{}
}
for _, v := range values {
if !util.In(v, currentValues) {
currentValues = append(currentValues, v)
}
}
o.ParamSet[k] = currentValues
}
}
// Encode the OrderedParamSet as a byte slice.
//
func (o *OrderedParamSet) Encode() ([]byte, error) {
var b bytes.Buffer
err := gob.NewEncoder(&b).Encode(o)
return b.Bytes(), err
}
// NewOrderedParamSetFromBytes creates an OrderedParamSet using a byte slice
// previously returned from Encode().
//
func NewOrderedParamSetFromBytes(b []byte) (*OrderedParamSet, error) {
p := NewOrderedParamSet()
buf := bytes.NewBuffer(b)
err := gob.NewDecoder(buf).Decode(&p)
return p, err
}
// buildEncoder builds a paramsEncoder for the current state of the OrderedParamSet.
func (o *OrderedParamSet) buildEncoder() *paramsEncoder {
ret := &paramsEncoder{
keyOrder: o.KeyOrder,
keys: map[string]string{},
values: map[string]map[string]string{},
}
for keyIndex, key := range o.KeyOrder {
keyString := strconv.Itoa(keyIndex)
ret.keys[key] = keyString
valueMap := map[string]string{}
for valueIndex, value := range o.ParamSet[key] {
valueMap[value] = strconv.Itoa(valueIndex)
}
ret.values[keyString] = valueMap
}
return ret
}
// buildDecoder builds a paramsDecoder for the current state of the OrderedParamSet.
func (o *OrderedParamSet) buildDecoder() *paramsDecoder {
ret := &paramsDecoder{
keys: map[string]string{},
values: map[string]map[string]string{},
}
for keyIndex, key := range o.KeyOrder {
keyString := strconv.Itoa(keyIndex)
ret.keys[keyString] = key
valueMap := map[string]string{}
for valueIndex, value := range o.ParamSet[key] {
valueMap[strconv.Itoa(valueIndex)] = value
}
ret.values[keyString] = valueMap
}
return ret
}
// paramsDecoder is built from the data in an OrderedParamSet and can efficiently
// decode Params for that OrderedParamSet.
//
// It builds up data structures that allow parsing params by lookup table.
type paramsDecoder struct {
// keys maps the offset, as a string, to the key name.
keys map[string]string
// values maps the key name to a map of value offset, as a string, to the value.
values map[string]map[string]string
}
// decodeStringToParams takes a string converts it to a Params.
//
// The input string representation is integer pairs as a structured key:
//
// ,0=1,1=3,3=0,
//
func (p *paramsDecoder) decodeStringToParams(s string) (Params, error) {
pairs := strings.Split(s, ",")
ret := make(Params, len(pairs))
for _, pair := range pairs {
if pair == "" {
continue
}
parts := strings.SplitN(pair, "=", 2)
if len(parts) != 2 {
return nil, skerr.Fmt("failed to parse: %s", pair)
}
key, ok := p.keys[parts[0]]
if !ok {
return nil, skerr.Fmt("failed to find key: %q", parts[0])
}
value, ok := p.values[parts[0]][parts[1]]
if !ok {
return nil, skerr.Fmt("failed to find value %q in %q (%q)", parts[1], p.values[parts[0]], parts[0])
}
ret[key] = value
}
return ret, nil
}
// paramsEncoder is built from the data in an OrderedParamSet and can efficiently
// encode Params for that OrderedParamSet.
//
// It builds up data structures that allow encoding Params by lookup table.
type paramsEncoder struct {
// keyOrder is the order of the keys.
keyOrder []string
// keys maps the key name to its offset in OrdereredParamSet.KeyOrder, the offset being
// given as a string, i.e. "2".
keys map[string]string
// values maps the key key offset to a map that maps the value to its offset, the offset being
// given as a string, i.e. "2".
values map[string]map[string]string
}
// encodeAsString takes a Params and finds the indexes of both the key and its value in
// the OrderedParamSet and then writes a string representation.
//
// The representation is integer pairs as a structured key:
//
// ,0=1,1=3,3=0,
func (p *paramsEncoder) encodeAsString(params Params) (string, error) {
ret := []string{","}
for _, key := range p.keyOrder {
value, ok := params[key]
if !ok {
continue
}
keyIndex, ok := p.keys[key]
if !ok {
return "", skerr.Fmt("Unknown key: %s", key)
}
valueIndex, ok := p.values[keyIndex][value]
if !ok {
return "", skerr.Fmt("Unknown value: %s", value)
}
ret = append(ret, keyIndex, "=", valueIndex, ",")
}
if len(ret) == 1 {
return "", skerr.Fmt("No params encoded.")
}
return strings.Join(ret, ""), nil
}
// encode takes a Params and encodes all the keys and values via
// the OrderedParamSet.
func (p *paramsEncoder) encode(params Params) (Params, error) {
ret := Params{}
for _, key := range p.keyOrder {
value, ok := params[key]
if !ok {
continue
}
keyIndex, ok := p.keys[key]
if !ok {
return nil, skerr.Fmt("Unknown key: %s", key)
}
valueIndex, ok := p.values[keyIndex][value]
if !ok {
return nil, skerr.Fmt("Unknown value: %s", value)
}
ret[keyIndex] = valueIndex
}
if len(ret) == 0 {
return nil, skerr.Fmt("No params encoded.")
}
return ret, nil
}
// encodeParamSet takes a ParamSet and encodes all the keys and values via the
// OrderedParamSet.
func (p *paramsEncoder) encodeParamSet(ps ParamSet) (ParamSet, error) {
ret := ParamSet{}
for _, key := range p.keyOrder {
values, ok := ps[key]
if !ok {
continue
}
keyIndex, ok := p.keys[key]
if !ok {
return nil, skerr.Fmt("Unknown key: %s", key)
}
arr, ok := ret[keyIndex]
if !ok {
arr = []string{}
}
for _, value := range values {
valueIndex, ok := p.values[keyIndex][value]
if !ok {
return nil, skerr.Fmt("Unknown value: %s", value)
}
arr = append(arr, valueIndex)
}
ret[keyIndex] = arr
}
if len(ret) == 0 {
return nil, skerr.Fmt("No params encoded.")
}
ret.Normalize()
return ret, nil
}
func (o *OrderedParamSet) getParamsEncoder() *paramsEncoder {
o.mutex.Lock()
defer o.mutex.Unlock()
if o.paramsEncoder == nil {
o.paramsEncoder = o.buildEncoder()
}
return o.paramsEncoder
}
// EncodeParamsAsString encodes the Params as a string containing indices.
func (o *OrderedParamSet) EncodeParamsAsString(p Params) (string, error) {
return o.getParamsEncoder().encodeAsString(p)
}
// EncodeParams encodes the Params via the OrderedParamSet.
func (o *OrderedParamSet) EncodeParams(p Params) (Params, error) {
return o.getParamsEncoder().encode(p)
}
// EncodeParamSet encodes the ParamSet via the OrderedParamSet.
func (o *OrderedParamSet) EncodeParamSet(p ParamSet) (ParamSet, error) {
return o.getParamsEncoder().encodeParamSet(p)
}
func (o *OrderedParamSet) getParamsDecoder() *paramsDecoder {
o.mutex.Lock()
defer o.mutex.Unlock()
if o.paramsDecoder == nil {
o.paramsDecoder = o.buildDecoder()
}
return o.paramsDecoder
}
// DecodeParamsFromString decodes the Params from a string.
func (o *OrderedParamSet) DecodeParamsFromString(s string) (Params, error) {
return o.getParamsDecoder().decodeStringToParams(s)
}