kubernetes/vendor/sigs.k8s.io/structured-merge-diff/v3/value/structreflect.go
2020-01-21 15:23:13 -08:00

270 lines
8.4 KiB
Go

/*
Copyright 2019 The Kubernetes 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 value
import (
"fmt"
"reflect"
"sync"
"sync/atomic"
)
// reflectStructCache keeps track of json tag related data for structs and fields to speed up reflection.
// TODO: This overlaps in functionality with the fieldCache in
// https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go#L57 but
// is more efficient at lookup by json field name. The logic should be consolidated. Only one copy of the cache needs
// to be kept for each running process.
var (
reflectStructCache = newStructCache()
)
type structCache struct {
// use an atomic and copy-on-write since there are a fixed (typically very small) number of structs compiled into any
// go program using this cache
value atomic.Value
// mu is held by writers when performing load/modify/store operations on the cache, readers do not need to hold a
// read-lock since the atomic value is always read-only
mu sync.Mutex
}
type structCacheMap map[reflect.Type]structCacheEntry
// structCacheEntry contains information about each struct field, keyed by json field name, that is expensive to
// compute using reflection.
type structCacheEntry map[string]*fieldCacheEntry
// Get returns true and fieldCacheEntry for the given type if the type is in the cache. Otherwise Get returns false.
func (c *structCache) Get(t reflect.Type) (map[string]*fieldCacheEntry, bool) {
entry, ok := c.value.Load().(structCacheMap)[t]
return entry, ok
}
// Set sets the fieldCacheEntry for the given type via a copy-on-write update to the struct cache.
func (c *structCache) Set(t reflect.Type, m map[string]*fieldCacheEntry) {
c.mu.Lock()
defer c.mu.Unlock()
currentCacheMap := c.value.Load().(structCacheMap)
if _, ok := currentCacheMap[t]; ok {
// Bail if the entry has been set while waiting for lock acquisition.
// This is safe since setting entries is idempotent.
return
}
newCacheMap := make(structCacheMap, len(currentCacheMap)+1)
for k, v := range currentCacheMap {
newCacheMap[k] = v
}
newCacheMap[t] = m
c.value.Store(newCacheMap)
}
func newStructCache() *structCache {
cache := &structCache{}
cache.value.Store(make(structCacheMap))
return cache
}
type fieldCacheEntry struct {
// isOmitEmpty is true if the field has the json 'omitempty' tag.
isOmitEmpty bool
// fieldPath is the field indices (see FieldByIndex) to lookup the value of
// a field in a reflect.Value struct. A path of field indices is used
// to support traversing to a field field in struct fields that have the 'inline'
// json tag.
fieldPath [][]int
}
func (f *fieldCacheEntry) getFieldFromStruct(structVal reflect.Value) reflect.Value {
// field might be field within 'inline' structs
for _, elem := range f.fieldPath {
structVal = structVal.FieldByIndex(elem)
}
return structVal
}
func getStructCacheEntry(t reflect.Type) structCacheEntry {
if hints, ok := reflectStructCache.Get(t); ok {
return hints
}
hints := map[string]*fieldCacheEntry{}
buildStructCacheEntry(t, hints, nil)
reflectStructCache.Set(t, hints)
return hints
}
func buildStructCacheEntry(t reflect.Type, infos map[string]*fieldCacheEntry, fieldPath [][]int) {
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
jsonName, omit, isInline, isOmitempty := lookupJsonTags(field)
if omit {
continue
}
if isInline {
buildStructCacheEntry(field.Type, infos, append(fieldPath, field.Index))
continue
}
info := &fieldCacheEntry{isOmitEmpty: isOmitempty, fieldPath: append(fieldPath, field.Index)}
infos[jsonName] = info
}
}
type structReflect struct {
valueReflect
}
func (r structReflect) Length() int {
i := 0
eachStructField(r.Value, func(s string, value reflect.Value) bool {
i++
return true
})
return i
}
func (r structReflect) Get(key string) (Value, bool) {
if val, ok, _ := r.findJsonNameField(key); ok {
return mustWrapValueReflect(val), true
}
return nil, false
}
func (r structReflect) Has(key string) bool {
_, ok, _ := r.findJsonNameField(key)
return ok
}
func (r structReflect) Set(key string, val Value) {
fieldEntry, ok := getStructCacheEntry(r.Value.Type())[key]
if !ok {
panic(fmt.Sprintf("key %s may not be set on struct %T: field does not exist", key, r.Value.Interface()))
}
oldVal := fieldEntry.getFieldFromStruct(r.Value)
newVal := reflect.ValueOf(val.Unstructured())
r.update(fieldEntry, key, oldVal, newVal)
}
func (r structReflect) Delete(key string) {
fieldEntry, ok := getStructCacheEntry(r.Value.Type())[key]
if !ok {
panic(fmt.Sprintf("key %s may not be deleted on struct %T: field does not exist", key, r.Value.Interface()))
}
oldVal := fieldEntry.getFieldFromStruct(r.Value)
if oldVal.Kind() != reflect.Ptr && !fieldEntry.isOmitEmpty {
panic(fmt.Sprintf("key %s may not be deleted on struct: %T: value is neither a pointer nor an omitempty field", key, r.Value.Interface()))
}
r.update(fieldEntry, key, oldVal, reflect.Zero(oldVal.Type()))
}
func (r structReflect) update(fieldEntry *fieldCacheEntry, key string, oldVal, newVal reflect.Value) {
if oldVal.CanSet() {
oldVal.Set(newVal)
return
}
// map items are not addressable, so if a struct is contained in a map, the only way to modify it is
// to write a replacement fieldEntry into the map.
if r.ParentMap != nil {
if r.ParentMapKey == nil {
panic("ParentMapKey must not be nil if ParentMap is not nil")
}
replacement := reflect.New(r.Value.Type()).Elem()
fieldEntry.getFieldFromStruct(replacement).Set(newVal)
r.ParentMap.SetMapIndex(*r.ParentMapKey, replacement)
return
}
// This should never happen since NewValueReflect ensures that the root object reflected on is a pointer and map
// item replacement is handled above.
panic(fmt.Sprintf("key %s may not be modified on struct: %T: struct is not settable", key, r.Value.Interface()))
}
func (r structReflect) Iterate(fn func(string, Value) bool) bool {
return eachStructField(r.Value, func(s string, value reflect.Value) bool {
v := mustWrapValueReflect(value)
defer v.Recycle()
return fn(s, v)
})
}
func eachStructField(structVal reflect.Value, fn func(string, reflect.Value) bool) bool {
for jsonName, fieldCacheEntry := range getStructCacheEntry(structVal.Type()) {
fieldVal := fieldCacheEntry.getFieldFromStruct(structVal)
if fieldCacheEntry.isOmitEmpty && (safeIsNil(fieldVal) || isZero(fieldVal)) {
// omit it
continue
}
ok := fn(jsonName, fieldVal)
if !ok {
return false
}
}
return true
}
func (r structReflect) Unstructured() interface{} {
// Use number of struct fields as a cheap way to rough estimate map size
result := make(map[string]interface{}, r.Value.NumField())
r.Iterate(func(s string, value Value) bool {
result[s] = value.Unstructured()
return true
})
return result
}
func (r structReflect) Equals(m Map) bool {
if rhsStruct, ok := m.(structReflect); ok {
return reflect.DeepEqual(r.Value.Interface(), rhsStruct.Value.Interface())
}
if r.Length() != m.Length() {
return false
}
structCacheEntry := getStructCacheEntry(r.Value.Type())
return m.Iterate(func(s string, value Value) bool {
fieldCacheEntry, ok := structCacheEntry[s]
if !ok {
return false
}
lhsVal := fieldCacheEntry.getFieldFromStruct(r.Value)
return Equals(mustWrapValueReflect(lhsVal), value)
})
}
func (r structReflect) findJsonNameFieldAndNotEmpty(jsonName string) (reflect.Value, bool) {
structCacheEntry, ok := getStructCacheEntry(r.Value.Type())[jsonName]
if !ok {
return reflect.Value{}, false
}
fieldVal := structCacheEntry.getFieldFromStruct(r.Value)
omit := structCacheEntry.isOmitEmpty && (safeIsNil(fieldVal) || isZero(fieldVal))
return fieldVal, !omit
}
func (r structReflect) findJsonNameField(jsonName string) (val reflect.Value, ok bool, omitEmpty bool) {
structCacheEntry, ok := getStructCacheEntry(r.Value.Type())[jsonName]
if !ok {
return reflect.Value{}, false, false
}
fieldVal := structCacheEntry.getFieldFromStruct(r.Value)
return fieldVal, true, structCacheEntry.isOmitEmpty
}