mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-03 23:22:25 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			214 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			214 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
 | 
						|
}
 |