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  }