resolver.go raw
1 /*
2 * Copyright 2021 ByteDance Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package resolver
18
19 import (
20 "fmt"
21 "reflect"
22 "strings"
23 "sync"
24 )
25
26 type FieldOpts int
27 type OffsetType int
28
29 const (
30 F_omitempty FieldOpts = 1 << iota
31 F_stringize
32 F_omitzero
33 )
34
35 const (
36 F_offset OffsetType = iota
37 F_deref
38 )
39
40 type Offset struct {
41 Size uintptr
42 Kind OffsetType
43 Type reflect.Type
44 }
45
46 type FieldMeta struct {
47 Name string
48 Path []Offset
49 Opts FieldOpts
50 Type reflect.Type
51 IsZero func(reflect.Value) bool
52 }
53
54 func (self *FieldMeta) String() string {
55 var path []string
56 var opts []string
57
58 /* dump the field path */
59 for _, off := range self.Path {
60 if off.Kind == F_offset {
61 path = append(path, fmt.Sprintf("%d", off.Size))
62 } else {
63 path = append(path, fmt.Sprintf("%d.(*%s)", off.Size, off.Type))
64 }
65 }
66
67 /* check for "string" */
68 if (self.Opts & F_stringize) != 0 {
69 opts = append(opts, "string")
70 }
71
72 /* check for "omitempty" */
73 if (self.Opts & F_omitempty) != 0 {
74 opts = append(opts, "omitempty")
75 }
76
77 /* format the field */
78 return fmt.Sprintf(
79 "{Field \"%s\" @ %s, opts=%s, type=%s}",
80 self.Name,
81 strings.Join(path, "."),
82 strings.Join(opts, ","),
83 self.Type,
84 )
85 }
86
87 func (self *FieldMeta) optimize() {
88 var n int
89 var v uintptr
90
91 /* merge adjacent offsets */
92 for _, o := range self.Path {
93 if v += o.Size; o.Kind == F_deref {
94 self.Path[n].Size = v
95 self.Path[n].Type, v = o.Type, 0
96 self.Path[n].Kind, n = F_deref, n + 1
97 }
98 }
99
100 /* last offset value */
101 if v != 0 {
102 self.Path[n].Size = v
103 self.Path[n].Type = nil
104 self.Path[n].Kind = F_offset
105 n++
106 }
107
108 /* must be at least 1 offset */
109 if n != 0 {
110 self.Path = self.Path[:n]
111 } else {
112 self.Path = []Offset{{Kind: F_offset}}
113 }
114 }
115
116 func resolveFields(vt reflect.Type) []FieldMeta {
117 tfv := typeFields(vt)
118 ret := []FieldMeta(nil)
119
120 /* convert each field */
121 for _, fv := range tfv.list {
122 /* add to result */
123 ret = append(ret, FieldMeta{})
124 fm := &ret[len(ret)-1]
125
126 item := vt
127 path := []Offset(nil)
128
129 /* check for "string" */
130 if fv.quoted {
131 fm.Opts |= F_stringize
132 }
133
134 /* check for "omitempty" */
135 if fv.omitEmpty {
136 fm.Opts |= F_omitempty
137 }
138
139 /* handle the "omitzero" */
140 handleOmitZero(fv, fm)
141
142 /* dump the field path */
143 for _, i := range fv.index {
144 kind := F_offset
145 fval := item.Field(i)
146 item = fval.Type
147
148 /* deref the pointer if needed */
149 if item.Kind() == reflect.Ptr {
150 kind = F_deref
151 item = item.Elem()
152 }
153
154 /* add to path */
155 path = append(path, Offset {
156 Kind: kind,
157 Type: item,
158 Size: fval.Offset,
159 })
160 }
161
162 /* get the index to the last offset */
163 idx := len(path) - 1
164 fvt := path[idx].Type
165
166 /* do not dereference into fields */
167 if path[idx].Kind == F_deref {
168 fvt = reflect.PtrTo(fvt)
169 path[idx].Kind = F_offset
170 }
171
172 fm.Type = fvt
173 fm.Path = path
174 fm.Name = fv.name
175 }
176
177 /* optimize the offsets */
178 for i := range ret {
179 ret[i].optimize()
180 }
181
182 /* all done */
183 return ret
184 }
185
186 var (
187 fieldLock = sync.RWMutex{}
188 fieldCache = map[reflect.Type][]FieldMeta{}
189 )
190
191 func ResolveStruct(vt reflect.Type) []FieldMeta {
192 var ok bool
193 var fm []FieldMeta
194
195 /* attempt to read from cache */
196 fieldLock.RLock()
197 fm, ok = fieldCache[vt]
198 fieldLock.RUnlock()
199
200 /* check if it was cached */
201 if ok {
202 return fm
203 }
204
205 /* otherwise use write-lock */
206 fieldLock.Lock()
207 defer fieldLock.Unlock()
208
209 /* double check */
210 if fm, ok = fieldCache[vt]; ok {
211 return fm
212 }
213
214 /* resolve the field */
215 fm = resolveFields(vt)
216 fieldCache[vt] = fm
217 return fm
218 }
219