github.com/etsc3259/etsc@v0.0.0-20190109113336-a9c2c10f9c95/rlp/typecache.go (about) 1 // Copyright 2014 The go-etsc Authors 2 // This file is part of the go-etsc library. 3 // 4 // The go-etsc library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-etsc library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-etsc library. If not, see <http://www.gnu.org/licenses/>. 16 17 package rlp 18 19 import ( 20 "fmt" 21 "reflect" 22 "strings" 23 "sync" 24 ) 25 26 var ( 27 typeCacheMutex sync.RWMutex 28 typeCache = make(map[typekey]*typeinfo) 29 ) 30 31 type typeinfo struct { 32 decoder 33 writer 34 } 35 36 // represents struct tags 37 type tags struct { 38 // rlp:"nil" controls whether empty input results in a nil pointer. 39 nilOK bool 40 // rlp:"tail" controls whether this field swallows additional list 41 // elements. It can only be set for the last field, which must be 42 // of slice type. 43 tail bool 44 // rlp:"-" ignores fields. 45 ignored bool 46 } 47 48 type typekey struct { 49 reflect.Type 50 // the key must include the struct tags because they 51 // might generate a different decoder. 52 tags 53 } 54 55 type decoder func(*Stream, reflect.Value) error 56 57 type writer func(reflect.Value, *encbuf) error 58 59 func cachedTypeInfo(typ reflect.Type, tags tags) (*typeinfo, error) { 60 typeCacheMutex.RLock() 61 info := typeCache[typekey{typ, tags}] 62 typeCacheMutex.RUnlock() 63 if info != nil { 64 return info, nil 65 } 66 // not in the cache, need to generate info for this type. 67 typeCacheMutex.Lock() 68 defer typeCacheMutex.Unlock() 69 return cachedTypeInfo1(typ, tags) 70 } 71 72 func cachedTypeInfo1(typ reflect.Type, tags tags) (*typeinfo, error) { 73 key := typekey{typ, tags} 74 info := typeCache[key] 75 if info != nil { 76 // another goroutine got the write lock first 77 return info, nil 78 } 79 // put a dummy value into the cache before generating. 80 // if the generator tries to lookup itself, it will get 81 // the dummy value and won't call itself recursively. 82 typeCache[key] = new(typeinfo) 83 info, err := genTypeInfo(typ, tags) 84 if err != nil { 85 // remove the dummy value if the generator fails 86 delete(typeCache, key) 87 return nil, err 88 } 89 *typeCache[key] = *info 90 return typeCache[key], err 91 } 92 93 type field struct { 94 index int 95 info *typeinfo 96 } 97 98 func structFields(typ reflect.Type) (fields []field, err error) { 99 for i := 0; i < typ.NumField(); i++ { 100 if f := typ.Field(i); f.PkgPath == "" { // exported 101 tags, err := parseStructTag(typ, i) 102 if err != nil { 103 return nil, err 104 } 105 if tags.ignored { 106 continue 107 } 108 info, err := cachedTypeInfo1(f.Type, tags) 109 if err != nil { 110 return nil, err 111 } 112 fields = append(fields, field{i, info}) 113 } 114 } 115 return fields, nil 116 } 117 118 func parseStructTag(typ reflect.Type, fi int) (tags, error) { 119 f := typ.Field(fi) 120 var ts tags 121 for _, t := range strings.Split(f.Tag.Get("rlp"), ",") { 122 switch t = strings.TrimSpace(t); t { 123 case "": 124 case "-": 125 ts.ignored = true 126 case "nil": 127 ts.nilOK = true 128 case "tail": 129 ts.tail = true 130 if fi != typ.NumField()-1 { 131 return ts, fmt.Errorf(`rlp: invalid struct tag "tail" for %v.%s (must be on last field)`, typ, f.Name) 132 } 133 if f.Type.Kind() != reflect.Slice { 134 return ts, fmt.Errorf(`rlp: invalid struct tag "tail" for %v.%s (field type is not slice)`, typ, f.Name) 135 } 136 default: 137 return ts, fmt.Errorf("rlp: unknown struct tag %q on %v.%s", t, typ, f.Name) 138 } 139 } 140 return ts, nil 141 } 142 143 func genTypeInfo(typ reflect.Type, tags tags) (info *typeinfo, err error) { 144 info = new(typeinfo) 145 if info.decoder, err = makeDecoder(typ, tags); err != nil { 146 return nil, err 147 } 148 if info.writer, err = makeWriter(typ, tags); err != nil { 149 return nil, err 150 } 151 return info, nil 152 } 153 154 func isUint(k reflect.Kind) bool { 155 return k >= reflect.Uint && k <= reflect.Uintptr 156 }