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