/* * 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 }