github.com/datachainlab/burrow@v0.25.0/event/query/reflect_tagged.go (about) 1 package query 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 "sync" 8 ) 9 10 type ReflectTagged struct { 11 rv reflect.Value 12 keys []string 13 ks map[string]struct{} 14 } 15 16 var _ Tagged = &ReflectTagged{} 17 18 func MustReflectTags(value interface{}, fieldNames ...string) *ReflectTagged { 19 rt, err := ReflectTags(value, fieldNames...) 20 if err != nil { 21 panic(err) 22 } 23 return rt 24 } 25 26 // ReflectTags provides a query.Tagged on a structs exported fields using query.StringFromValue to derive the string 27 // values associated with each field. If passed explicit field names will only only provide those fields as tags, 28 // otherwise all exported fields are provided. 29 func ReflectTags(value interface{}, fieldNames ...string) (*ReflectTagged, error) { 30 rv := reflect.ValueOf(value) 31 if rv.IsNil() { 32 return &ReflectTagged{}, nil 33 } 34 if rv.Kind() != reflect.Ptr { 35 return nil, fmt.Errorf("ReflectStructTags needs a pointer to a struct but %v is not a pointer", 36 rv.Interface()) 37 } 38 if rv.Elem().Kind() != reflect.Struct { 39 return nil, fmt.Errorf("ReflectStructTags needs a pointer to a struct but %v does not point to a struct", 40 rv.Interface()) 41 } 42 ty := rv.Elem().Type() 43 // Try our global cache on types 44 if rt, ok := cache.get(ty, fieldNames); ok { 45 rt.rv = rv 46 return rt, nil 47 } 48 49 numField := ty.NumField() 50 if len(fieldNames) > 0 { 51 if len(fieldNames) > numField { 52 return nil, fmt.Errorf("ReflectTags asked to tag %v fields but %v only has %v fields", 53 len(fieldNames), rv.Interface(), numField) 54 } 55 numField = len(fieldNames) 56 } 57 rt := &ReflectTagged{ 58 rv: rv, 59 ks: make(map[string]struct{}, numField), 60 keys: make([]string, 0, numField), 61 } 62 if len(fieldNames) > 0 { 63 for _, fieldName := range fieldNames { 64 field, ok := ty.FieldByName(fieldName) 65 if !ok { 66 return nil, fmt.Errorf("ReflectTags asked to tag field named %s by no such field on %v", 67 fieldName, rv.Interface()) 68 } 69 ok = rt.registerField(field) 70 if !ok { 71 return nil, fmt.Errorf("field %s of %v is not exported so cannot act as tag", fieldName, 72 rv.Interface()) 73 } 74 } 75 } else { 76 for i := 0; i < numField; i++ { 77 rt.registerField(ty.Field(i)) 78 } 79 } 80 // Cache the registration 81 cache.put(ty, rt, fieldNames) 82 return rt, nil 83 } 84 85 func (rt *ReflectTagged) registerField(field reflect.StructField) (ok bool) { 86 // Empty iff struct field is exported 87 if field.PkgPath == "" { 88 rt.keys = append(rt.keys, field.Name) 89 rt.ks[field.Name] = struct{}{} 90 return true 91 } 92 return false 93 } 94 95 func (rt *ReflectTagged) Keys() []string { 96 return rt.keys 97 } 98 99 func (rt *ReflectTagged) Get(key string) (value string, ok bool) { 100 if _, ok := rt.ks[key]; ok { 101 return StringFromValue(rt.rv.Elem().FieldByName(key).Interface()), true 102 } 103 return "", false 104 } 105 106 func (rt *ReflectTagged) Len() int { 107 return len(rt.keys) 108 } 109 110 type reflectTaggedCache struct { 111 sync.Mutex 112 rts map[reflect.Type]map[string]ReflectTagged 113 } 114 115 // Avoid the need to iterate over reflected type each time we need a reflect tagged 116 var cache = &reflectTaggedCache{ 117 rts: make(map[reflect.Type]map[string]ReflectTagged), 118 } 119 120 func (c *reflectTaggedCache) get(ty reflect.Type, keys []string) (*ReflectTagged, bool) { 121 c.Lock() 122 defer c.Unlock() 123 if _, ok := c.rts[ty]; ok { 124 key := strings.Join(keys, ",") 125 if rt, ok := c.rts[ty][key]; ok { 126 return &rt, true 127 } 128 } 129 return nil, false 130 } 131 132 func (c *reflectTaggedCache) put(ty reflect.Type, rt *ReflectTagged, fieldNames []string) { 133 c.Lock() 134 defer c.Unlock() 135 if _, ok := c.rts[ty]; !ok { 136 c.rts[ty] = make(map[string]ReflectTagged) 137 } 138 key := strings.Join(fieldNames, ",") 139 c.rts[ty][key] = *rt 140 }