215 lines
4.6 KiB
Go
215 lines
4.6 KiB
Go
/*
|
|
* Copyright 2021 ByteDance Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package resolver
|
|
|
|
import (
|
|
`fmt`
|
|
`reflect`
|
|
`strings`
|
|
`sync`
|
|
)
|
|
|
|
type FieldOpts int
|
|
type OffsetType int
|
|
|
|
const (
|
|
F_omitempty FieldOpts = 1 << iota
|
|
F_stringize
|
|
)
|
|
|
|
const (
|
|
F_offset OffsetType = iota
|
|
F_deref
|
|
)
|
|
|
|
type Offset struct {
|
|
Size uintptr
|
|
Kind OffsetType
|
|
Type reflect.Type
|
|
}
|
|
|
|
type FieldMeta struct {
|
|
Name string
|
|
Path []Offset
|
|
Opts FieldOpts
|
|
Type reflect.Type
|
|
}
|
|
|
|
func (self *FieldMeta) String() string {
|
|
var path []string
|
|
var opts []string
|
|
|
|
/* dump the field path */
|
|
for _, off := range self.Path {
|
|
if off.Kind == F_offset {
|
|
path = append(path, fmt.Sprintf("%d", off.Size))
|
|
} else {
|
|
path = append(path, fmt.Sprintf("%d.(*%s)", off.Size, off.Type))
|
|
}
|
|
}
|
|
|
|
/* check for "string" */
|
|
if (self.Opts & F_stringize) != 0 {
|
|
opts = append(opts, "string")
|
|
}
|
|
|
|
/* check for "omitempty" */
|
|
if (self.Opts & F_omitempty) != 0 {
|
|
opts = append(opts, "omitempty")
|
|
}
|
|
|
|
/* format the field */
|
|
return fmt.Sprintf(
|
|
"{Field \"%s\" @ %s, opts=%s, type=%s}",
|
|
self.Name,
|
|
strings.Join(path, "."),
|
|
strings.Join(opts, ","),
|
|
self.Type,
|
|
)
|
|
}
|
|
|
|
func (self *FieldMeta) optimize() {
|
|
var n int
|
|
var v uintptr
|
|
|
|
/* merge adjacent offsets */
|
|
for _, o := range self.Path {
|
|
if v += o.Size; o.Kind == F_deref {
|
|
self.Path[n].Size = v
|
|
self.Path[n].Type, v = o.Type, 0
|
|
self.Path[n].Kind, n = F_deref, n + 1
|
|
}
|
|
}
|
|
|
|
/* last offset value */
|
|
if v != 0 {
|
|
self.Path[n].Size = v
|
|
self.Path[n].Type = nil
|
|
self.Path[n].Kind = F_offset
|
|
n++
|
|
}
|
|
|
|
/* must be at least 1 offset */
|
|
if n != 0 {
|
|
self.Path = self.Path[:n]
|
|
} else {
|
|
self.Path = []Offset{{Kind: F_offset}}
|
|
}
|
|
}
|
|
|
|
func resolveFields(vt reflect.Type) []FieldMeta {
|
|
tfv := typeFields(vt)
|
|
ret := []FieldMeta(nil)
|
|
|
|
/* convert each field */
|
|
for _, fv := range tfv.list {
|
|
item := vt
|
|
path := []Offset(nil)
|
|
opts := FieldOpts(0)
|
|
|
|
/* check for "string" */
|
|
if fv.quoted {
|
|
opts |= F_stringize
|
|
}
|
|
|
|
/* check for "omitempty" */
|
|
if fv.omitEmpty {
|
|
opts |= F_omitempty
|
|
}
|
|
|
|
/* dump the field path */
|
|
for _, i := range fv.index {
|
|
kind := F_offset
|
|
fval := item.Field(i)
|
|
item = fval.Type
|
|
|
|
/* deref the pointer if needed */
|
|
if item.Kind() == reflect.Ptr {
|
|
kind = F_deref
|
|
item = item.Elem()
|
|
}
|
|
|
|
/* add to path */
|
|
path = append(path, Offset {
|
|
Kind: kind,
|
|
Type: item,
|
|
Size: fval.Offset,
|
|
})
|
|
}
|
|
|
|
/* get the index to the last offset */
|
|
idx := len(path) - 1
|
|
fvt := path[idx].Type
|
|
|
|
/* do not dereference into fields */
|
|
if path[idx].Kind == F_deref {
|
|
fvt = reflect.PtrTo(fvt)
|
|
path[idx].Kind = F_offset
|
|
}
|
|
|
|
/* add to result */
|
|
ret = append(ret, FieldMeta {
|
|
Type: fvt,
|
|
Opts: opts,
|
|
Path: path,
|
|
Name: fv.name,
|
|
})
|
|
}
|
|
|
|
/* optimize the offsets */
|
|
for i := range ret {
|
|
ret[i].optimize()
|
|
}
|
|
|
|
/* all done */
|
|
return ret
|
|
}
|
|
|
|
var (
|
|
fieldLock = sync.RWMutex{}
|
|
fieldCache = map[reflect.Type][]FieldMeta{}
|
|
)
|
|
|
|
func ResolveStruct(vt reflect.Type) []FieldMeta {
|
|
var ok bool
|
|
var fm []FieldMeta
|
|
|
|
/* attempt to read from cache */
|
|
fieldLock.RLock()
|
|
fm, ok = fieldCache[vt]
|
|
fieldLock.RUnlock()
|
|
|
|
/* check if it was cached */
|
|
if ok {
|
|
return fm
|
|
}
|
|
|
|
/* otherwise use write-lock */
|
|
fieldLock.Lock()
|
|
defer fieldLock.Unlock()
|
|
|
|
/* double check */
|
|
if fm, ok = fieldCache[vt]; ok {
|
|
return fm
|
|
}
|
|
|
|
/* resolve the field */
|
|
fm = resolveFields(vt)
|
|
fieldCache[vt] = fm
|
|
return fm
|
|
}
|