mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			270 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			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
 | 
						|
}
 |