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