github.com/pubgo/xprocess@v0.1.11/xprocess_schema/cache.go (about)

     1  // Copyright 2012 The Gorilla Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package xprocess_schema
     6  
     7  import (
     8  	"errors"
     9  	"reflect"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  )
    14  
    15  var invalidPath = errors.New("schema: invalid path")
    16  
    17  // newCache returns a new cache.
    18  func newCache() *cache {
    19  	c := cache{
    20  		m:       make(map[reflect.Type]*structInfo),
    21  		regconv: make(map[reflect.Type]Converter),
    22  		tag:     "schema",
    23  	}
    24  	return &c
    25  }
    26  
    27  // cache caches meta-data about a struct.
    28  type cache struct {
    29  	l       sync.RWMutex
    30  	m       map[reflect.Type]*structInfo
    31  	regconv map[reflect.Type]Converter
    32  	tag     string
    33  }
    34  
    35  // registerConverter registers a converter function for a custom type.
    36  func (c *cache) registerConverter(value interface{}, converterFunc Converter) {
    37  	c.regconv[reflect.TypeOf(value)] = converterFunc
    38  }
    39  
    40  // parsePath parses a path in dotted notation verifying that it is a valid
    41  // path to a struct field.
    42  //
    43  // It returns "path parts" which contain indices to fields to be used by
    44  // reflect.Value.FieldByString(). Multiple parts are required for slices of
    45  // structs.
    46  func (c *cache) parsePath(p string, t reflect.Type) ([]pathPart, error) {
    47  	var struc *structInfo
    48  	var field *fieldInfo
    49  	var index64 int64
    50  	var err error
    51  	parts := make([]pathPart, 0)
    52  	path := make([]string, 0)
    53  	keys := strings.Split(p, ".")
    54  	for i := 0; i < len(keys); i++ {
    55  		if t.Kind() != reflect.Struct {
    56  			return nil, invalidPath
    57  		}
    58  		if struc = c.get(t); struc == nil {
    59  			return nil, invalidPath
    60  		}
    61  		if field = struc.get(keys[i]); field == nil {
    62  			return nil, invalidPath
    63  		}
    64  		// Valid field. Append index.
    65  		path = append(path, field.name)
    66  		if field.isSliceOfStructs && (!field.unmarshalerInfo.IsValid || (field.unmarshalerInfo.IsValid && field.unmarshalerInfo.IsSliceElement)) {
    67  			// Parse a special case: slices of structs.
    68  			// i+1 must be the slice index.
    69  			//
    70  			// Now that struct can implements TextUnmarshaler interface,
    71  			// we don't need to force the struct's fields to appear in the path.
    72  			// So checking i+2 is not necessary anymore.
    73  			i++
    74  			if i+1 > len(keys) {
    75  				return nil, invalidPath
    76  			}
    77  			if index64, err = strconv.ParseInt(keys[i], 10, 0); err != nil {
    78  				return nil, invalidPath
    79  			}
    80  			parts = append(parts, pathPart{
    81  				path:  path,
    82  				field: field,
    83  				index: int(index64),
    84  			})
    85  			path = make([]string, 0)
    86  
    87  			// Get the next struct type, dropping ptrs.
    88  			if field.typ.Kind() == reflect.Ptr {
    89  				t = field.typ.Elem()
    90  			} else {
    91  				t = field.typ
    92  			}
    93  			if t.Kind() == reflect.Slice {
    94  				t = t.Elem()
    95  				if t.Kind() == reflect.Ptr {
    96  					t = t.Elem()
    97  				}
    98  			}
    99  		} else if field.typ.Kind() == reflect.Ptr {
   100  			t = field.typ.Elem()
   101  		} else {
   102  			t = field.typ
   103  		}
   104  	}
   105  	// Add the remaining.
   106  	parts = append(parts, pathPart{
   107  		path:  path,
   108  		field: field,
   109  		index: -1,
   110  	})
   111  	return parts, nil
   112  }
   113  
   114  // get returns a cached structInfo, creating it if necessary.
   115  func (c *cache) get(t reflect.Type) *structInfo {
   116  	c.l.RLock()
   117  	info := c.m[t]
   118  	c.l.RUnlock()
   119  	if info == nil {
   120  		info = c.create(t, "")
   121  		c.l.Lock()
   122  		c.m[t] = info
   123  		c.l.Unlock()
   124  	}
   125  	return info
   126  }
   127  
   128  // create creates a structInfo with meta-data about a struct.
   129  func (c *cache) create(t reflect.Type, parentAlias string) *structInfo {
   130  	info := &structInfo{}
   131  	var anonymousInfos []*structInfo
   132  	for i := 0; i < t.NumField(); i++ {
   133  		if f := c.createField(t.Field(i), parentAlias); f != nil {
   134  			info.fields = append(info.fields, f)
   135  			if ft := indirectType(f.typ); ft.Kind() == reflect.Struct && f.isAnonymous {
   136  				anonymousInfos = append(anonymousInfos, c.create(ft, f.canonicalAlias))
   137  			}
   138  		}
   139  	}
   140  	for i, a := range anonymousInfos {
   141  		others := []*structInfo{info}
   142  		others = append(others, anonymousInfos[:i]...)
   143  		others = append(others, anonymousInfos[i+1:]...)
   144  		for _, f := range a.fields {
   145  			if !containsAlias(others, f.alias) {
   146  				info.fields = append(info.fields, f)
   147  			}
   148  		}
   149  	}
   150  	return info
   151  }
   152  
   153  // createField creates a fieldInfo for the given field.
   154  func (c *cache) createField(field reflect.StructField, parentAlias string) *fieldInfo {
   155  	alias, options := fieldAlias(field, c.tag)
   156  	if alias == "-" {
   157  		// Ignore this field.
   158  		return nil
   159  	}
   160  	canonicalAlias := alias
   161  	if parentAlias != "" {
   162  		canonicalAlias = parentAlias + "." + alias
   163  	}
   164  	// Check if the type is supported and don't cache it if not.
   165  	// First let's get the basic type.
   166  	isSlice, isStruct := false, false
   167  	ft := field.Type
   168  	m := isTextUnmarshaler(reflect.Zero(ft))
   169  	if ft.Kind() == reflect.Ptr {
   170  		ft = ft.Elem()
   171  	}
   172  	if isSlice = ft.Kind() == reflect.Slice; isSlice {
   173  		ft = ft.Elem()
   174  		if ft.Kind() == reflect.Ptr {
   175  			ft = ft.Elem()
   176  		}
   177  	}
   178  	if ft.Kind() == reflect.Array {
   179  		ft = ft.Elem()
   180  		if ft.Kind() == reflect.Ptr {
   181  			ft = ft.Elem()
   182  		}
   183  	}
   184  	if isStruct = ft.Kind() == reflect.Struct; !isStruct {
   185  		if c.converter(ft) == nil && builtinConverters[ft.Kind()] == nil {
   186  			// Type is not supported.
   187  			return nil
   188  		}
   189  	}
   190  
   191  	return &fieldInfo{
   192  		typ:              field.Type,
   193  		name:             field.Name,
   194  		alias:            alias,
   195  		canonicalAlias:   canonicalAlias,
   196  		unmarshalerInfo:  m,
   197  		isSliceOfStructs: isSlice && isStruct,
   198  		isAnonymous:      field.Anonymous,
   199  		isRequired:       options.Contains("required"),
   200  	}
   201  }
   202  
   203  // converter returns the converter for a type.
   204  func (c *cache) converter(t reflect.Type) Converter {
   205  	return c.regconv[t]
   206  }
   207  
   208  // ----------------------------------------------------------------------------
   209  
   210  type structInfo struct {
   211  	fields []*fieldInfo
   212  }
   213  
   214  func (i *structInfo) get(alias string) *fieldInfo {
   215  	for _, field := range i.fields {
   216  		if strings.EqualFold(field.alias, alias) {
   217  			return field
   218  		}
   219  	}
   220  	return nil
   221  }
   222  
   223  func containsAlias(infos []*structInfo, alias string) bool {
   224  	for _, info := range infos {
   225  		if info.get(alias) != nil {
   226  			return true
   227  		}
   228  	}
   229  	return false
   230  }
   231  
   232  type fieldInfo struct {
   233  	typ reflect.Type
   234  	// name is the field name in the struct.
   235  	name  string
   236  	alias string
   237  	// canonicalAlias is almost the same as the alias, but is prefixed with
   238  	// an embedded struct field alias in dotted notation if this field is
   239  	// promoted from the struct.
   240  	// For instance, if the alias is "N" and this field is an embedded field
   241  	// in a struct "X", canonicalAlias will be "X.N".
   242  	canonicalAlias string
   243  	// unmarshalerInfo contains information regarding the
   244  	// encoding.TextUnmarshaler implementation of the field type.
   245  	unmarshalerInfo unmarshaler
   246  	// isSliceOfStructs indicates if the field type is a slice of structs.
   247  	isSliceOfStructs bool
   248  	// isAnonymous indicates whether the field is embedded in the struct.
   249  	isAnonymous bool
   250  	isRequired  bool
   251  }
   252  
   253  func (f *fieldInfo) paths(prefix string) []string {
   254  	if f.alias == f.canonicalAlias {
   255  		return []string{prefix + f.alias}
   256  	}
   257  	return []string{prefix + f.alias, prefix + f.canonicalAlias}
   258  }
   259  
   260  type pathPart struct {
   261  	field *fieldInfo
   262  	path  []string // path to the field: walks structs using field names.
   263  	index int      // struct index in slices of structs.
   264  }
   265  
   266  // ----------------------------------------------------------------------------
   267  
   268  func indirectType(typ reflect.Type) reflect.Type {
   269  	if typ.Kind() == reflect.Ptr {
   270  		return typ.Elem()
   271  	}
   272  	return typ
   273  }
   274  
   275  // fieldAlias parses a field tag to get a field alias.
   276  func fieldAlias(field reflect.StructField, tagName string) (alias string, options tagOptions) {
   277  	if tag := field.Tag.Get(tagName); tag != "" {
   278  		alias, options = parseTag(tag)
   279  	}
   280  	if alias == "" {
   281  		alias = field.Name
   282  	}
   283  	return alias, options
   284  }
   285  
   286  // tagOptions is the string following a comma in a struct field's tag, or
   287  // the empty string. It does not include the leading comma.
   288  type tagOptions []string
   289  
   290  // parseTag splits a struct field's url tag into its name and comma-separated
   291  // options.
   292  func parseTag(tag string) (string, tagOptions) {
   293  	s := strings.Split(tag, ",")
   294  	return s[0], s[1:]
   295  }
   296  
   297  // Contains checks whether the tagOptions contains the specified option.
   298  func (o tagOptions) Contains(option string) bool {
   299  	for _, s := range o {
   300  		if s == option {
   301  			return true
   302  		}
   303  	}
   304  	return false
   305  }