github.com/searKing/golang/go@v1.2.117/reflect/structinfo/structinfo.go (about)

     1  // Copyright 2020 The searKing Author. 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 structinfo
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"reflect"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/searKing/golang/go/container/lru"
    15  )
    16  
    17  // --------------------------------------------------------------------------
    18  // Maintain a mapping of keys to structure field indexes
    19  
    20  type StructInfo struct {
    21  	fieldsLRU lru.LRU
    22  	Zero      reflect.Value
    23  }
    24  type fieldInfo struct {
    25  	Key  string
    26  	Num  int
    27  	Tags map[string]string
    28  }
    29  
    30  var structMap = make(map[reflect.Type]*StructInfo)
    31  var structMapMutex sync.RWMutex
    32  
    33  type externalPanic string
    34  
    35  func (e externalPanic) String() string {
    36  	return string(e)
    37  }
    38  func IsStructFieldPrivate(field reflect.StructField) bool {
    39  	if field.PkgPath != "" && !field.Anonymous {
    40  		return true // Private field
    41  	}
    42  	return false
    43  }
    44  
    45  // Single Instance
    46  // Ignore the private field
    47  func GetStructInfo(st reflect.Type) (*StructInfo, error) {
    48  	// lazyFound
    49  	structMapMutex.RLock()
    50  	sinfo, found := structMap[st]
    51  	structMapMutex.RUnlock()
    52  	if found {
    53  		return sinfo, nil
    54  	}
    55  
    56  	// Traversal all Fields of the Struct
    57  	n := st.NumField()
    58  	fieldsLRU := lru.LRU{}
    59  	inlineMap := -1
    60  	for i := 0; i != n; i++ {
    61  		field := st.Field(i)
    62  		if IsStructFieldPrivate(field) {
    63  			continue // Private field
    64  		}
    65  
    66  		info := fieldInfo{Num: i}
    67  
    68  		tag := field.Tag.Get("bson")
    69  		if tag == "" && strings.Index(string(field.Tag), ":") < 0 {
    70  			tag = string(field.Tag)
    71  		}
    72  		if tag == "-" {
    73  			continue
    74  		}
    75  
    76  		inline := false
    77  		fields := strings.Split(tag, ",")
    78  		if len(fields) > 1 {
    79  			for _, flag := range fields[1:] {
    80  				switch flag {
    81  				case "omitempty":
    82  					info.Tags["OmitEmpty"] = "omitempty"
    83  				case "inline":
    84  					inline = true
    85  				default:
    86  					msg := fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st)
    87  					panic(externalPanic(msg))
    88  				}
    89  			}
    90  			tag = fields[0]
    91  		}
    92  
    93  		if inline {
    94  			switch field.Type.Kind() {
    95  			case reflect.Map:
    96  				if inlineMap >= 0 {
    97  					return nil, errors.New("Multiple ,inline maps in struct " + st.String())
    98  				}
    99  				if field.Type.Key() != reflect.TypeOf("") {
   100  					return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String())
   101  				}
   102  				inlineMap = info.Num
   103  			case reflect.Struct:
   104  				sinfo, err := GetStructInfo(field.Type)
   105  				if err != nil {
   106  					return nil, err
   107  				}
   108  
   109  				for _, finfoPair := range sinfo.fieldsLRU.Pairs() {
   110  					if _, found := fieldsLRU.Find(finfoPair.Key); found {
   111  						msg := "Duplicated key '" + finfoPair.Key.(string) + "' in struct " + st.String()
   112  						return nil, errors.New(msg)
   113  					}
   114  					fieldsLRU.AddPair(finfoPair)
   115  				}
   116  			default:
   117  				panic("Option ,inline needs a struct value or map field")
   118  			}
   119  			continue
   120  		}
   121  
   122  		if tag != "" {
   123  			info.Key = tag
   124  		} else {
   125  			info.Key = strings.ToLower(field.Name)
   126  		}
   127  
   128  		if _, found = fieldsLRU.Find(info.Key); found {
   129  			msg := "Duplicated key '" + info.Key + "' in struct " + st.String()
   130  			return nil, errors.New(msg)
   131  		}
   132  		fieldsLRU.Add(info.Key, info)
   133  	}
   134  	sinfo = &StructInfo{
   135  		fieldsLRU,
   136  		reflect.New(st).Elem(),
   137  	}
   138  	structMapMutex.Lock()
   139  	structMap[st] = sinfo
   140  	structMapMutex.Unlock()
   141  	return sinfo, nil
   142  }