gotype/fold_reflect.go (400 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 gotype
import (
"reflect"
"strings"
"unicode"
"unicode/utf8"
structform "github.com/elastic/go-structform"
)
type typeFoldRegistry struct {
// mu sync.RWMutex
m map[typeFoldKey]reFoldFn
}
type typeFoldKey struct {
ty reflect.Type
inline bool
}
var _foldRegistry = newTypeFoldRegistry()
func getReflectFold(c *foldContext, t reflect.Type) (reFoldFn, error) {
var err error
f := c.reg.find(t)
if f != nil {
return f, nil
}
f = getReflectFoldPrimitive(t)
if f != nil {
c.reg.set(t, f)
return f, nil
}
if implementsFolder(t) || implementsPtrFolder(t) {
f := reFoldFolderIfc
c.reg.set(t, f)
return f, nil
}
switch t.Kind() {
case reflect.Ptr:
f, err = getFoldPointer(c, t)
case reflect.Struct:
f, err = getReflectFoldStruct(c, t, false)
case reflect.Map:
f, err = getReflectFoldMap(c, t)
case reflect.Slice, reflect.Array:
f, err = getReflectFoldSlice(c, t)
case reflect.Interface:
f, err = getReflectFoldElem(c, t)
default:
f, err = getReflectFoldPrimitiveKind(t)
if err != nil {
return nil, err
}
}
if err != nil {
return nil, err
}
c.reg.set(t, f)
return f, nil
}
func getReflectFoldMap(c *foldContext, t reflect.Type) (reFoldFn, error) {
iterVisitor, err := getReflectFoldMapKeys(c, t)
if err != nil {
return nil, err
}
return func(C *foldContext, rv reflect.Value) error {
if err := C.OnObjectStart(rv.Len(), structform.AnyType); err != nil {
return err
}
if err := iterVisitor(C, rv); err != nil {
return err
}
return C.OnObjectFinished()
}, nil
}
func getFoldPointer(c *foldContext, t reflect.Type) (reFoldFn, error) {
N, bt := baseType(t)
elemVisitor, err := getReflectFold(c, bt)
if err != nil {
return nil, err
}
return makePointerFold(N, elemVisitor), nil
}
func makePointerFold(N int, elemVisitor reFoldFn) reFoldFn {
if N == 0 {
return elemVisitor
}
return func(C *foldContext, v reflect.Value) error {
for i := 0; i < N; i++ {
if v.IsNil() {
return C.OnNil()
}
v = v.Elem()
}
return elemVisitor(C, v)
}
}
func getReflectFoldElem(c *foldContext, t reflect.Type) (reFoldFn, error) {
return foldInterfaceElem, nil
}
func foldInterfaceElem(C *foldContext, v reflect.Value) error {
if v.Kind() != reflect.Interface {
return foldAnyReflect(C, v)
}
if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && v.IsNil() {
return C.visitor.OnNil()
}
return foldAnyReflect(C, v.Elem())
}
func getReflectFoldStruct(c *foldContext, t reflect.Type, inline bool) (reFoldFn, error) {
fields, err := getStructFieldsFolds(c, t)
if err != nil {
return nil, err
}
if inline {
return makeFieldsFold(fields), nil
}
return makeStructFold(fields), nil
}
// TODO: benchmark field accessors based on pointer offsets
func getStructFieldsFolds(c *foldContext, t reflect.Type) ([]reFoldFn, error) {
count := t.NumField()
fields := make([]reFoldFn, 0, count)
for i := 0; i < count; i++ {
fv, err := buildFieldFold(c, t, i)
if err != nil {
return nil, err
}
if fv == nil {
continue
}
fields = append(fields, fv)
}
if len(fields) < cap(fields) {
tmp := make([]reFoldFn, len(fields))
copy(tmp, fields)
fields = tmp
}
return fields, nil
}
func makeStructFold(fields []reFoldFn) reFoldFn {
fieldsVisitor := makeFieldsFold(fields)
return func(C *foldContext, v reflect.Value) error {
if err := C.OnObjectStart(len(fields), structform.AnyType); err != nil {
return err
}
if err := fieldsVisitor(C, v); err != nil {
return err
}
return C.OnObjectFinished()
}
}
func makeFieldsFold(fields []reFoldFn) reFoldFn {
return func(C *foldContext, v reflect.Value) error {
for _, fv := range fields {
if err := fv(C, v); err != nil {
return err
}
}
return nil
}
}
func buildFieldFold(C *foldContext, t reflect.Type, idx int) (reFoldFn, error) {
st := t.Field(idx)
name := st.Name
rune, _ := utf8.DecodeRuneInString(name)
if !unicode.IsUpper(rune) {
// ignore non exported fields
return nil, nil
}
tagName, tagOpts := parseTags(st.Tag.Get(C.opts.tag))
if tagOpts.squash && tagOpts.omitEmpty {
return nil, errInlineAndOmitEmpty
}
if tagOpts.omit {
// ignore omitted fields
return nil, nil
}
if tagOpts.squash {
return buildFieldFoldInline(C, t, idx)
}
foldT := st.Type
if tagOpts.omitEmpty {
_, foldT = baseType(st.Type)
}
valueVisitor, err := getReflectFold(C, foldT)
if err != nil {
return nil, err
}
if tagName != "" {
name = tagName
} else {
name = strings.ToLower(name)
}
if tagOpts.omitEmpty {
return makeNonEmptyFieldFold(name, idx, st.Type, valueVisitor)
}
return makeFieldFold(name, idx, valueVisitor)
}
func buildFieldFoldInline(
C *foldContext,
t reflect.Type,
idx int,
) (reFoldFn, error) {
var (
st = t.Field(idx)
N, bt = baseType(st.Type)
baseVisitor reFoldFn
err error
)
f := C.reg.findInline(st.Type)
if f != nil {
return makeFieldInlineFold(idx, f), nil
}
baseVisitor = C.reg.findInline(bt)
if baseVisitor == nil {
baseVisitor, err = fieldFoldGenInline(C, bt)
if err != nil {
return nil, err
}
C.reg.setInline(bt, baseVisitor)
}
f = makePointerFold(N, baseVisitor)
C.reg.setInline(st.Type, f)
return makeFieldInlineFold(idx, f), nil
}
func fieldFoldGenInline(C *foldContext, t reflect.Type) (reFoldFn, error) {
if C.userReg != nil {
if f := C.userReg[t]; f != nil {
f = embeddObjReFold(C, f)
}
}
if implementsFolder(t) || implementsPtrFolder(t) {
return embeddObjReFold(C, reFoldFolderIfc), nil
}
switch t.Kind() {
case reflect.Struct:
return getReflectFoldStruct(C, t, true)
case reflect.Map:
return getReflectFoldMapKeys(C, t)
case reflect.Interface:
return getReflectFoldInlineInterface(C, t)
}
return nil, errSquashNeedObject
}
func makeFieldFold(name string, idx int, fn reFoldFn) (reFoldFn, error) {
return func(C *foldContext, v reflect.Value) error {
if err := C.OnKey(name); err != nil {
return err
}
return fn(C, v.Field(idx))
}, nil
}
func makeFieldInlineFold(idx int, fn reFoldFn) reFoldFn {
return func(C *foldContext, v reflect.Value) error {
return fn(C, v.Field(idx))
}
}
func makeNonEmptyFieldFold(name string, idx int, t reflect.Type, fn reFoldFn) (reFoldFn, error) {
resolver := makeResolveNonEmptyValue(t)
if resolver == nil {
return makeFieldFold(name, idx, fn)
}
return func(C *foldContext, v reflect.Value) (err error) {
field, ok := resolver(v.Field(idx))
if ok {
if err = C.OnKey(name); err != nil {
return
}
err = fn(C, field)
}
return
}, nil
}
func makeResolveNonEmptyValue(st reflect.Type) func(reflect.Value) (reflect.Value, bool) {
type resolver func(reflect.Value) (reflect.Value, bool)
resolveBySize := func(v reflect.Value) (reflect.Value, bool) {
return v, v.Len() > 0
}
resolveIsZeroer := func(v reflect.Value) (reflect.Value, bool) {
empty := v.Interface().(IsZeroer).IsZero()
return v, !empty
}
resolveIsZeroerPtr := func(v reflect.Value) (reflect.Value, bool) {
if v.CanAddr() {
return resolveIsZeroer(v.Addr())
}
tmp := reflect.New(v.Type())
tmp.Elem().Set(v)
return resolveIsZeroer(tmp)
}
resolveInterfaceLazy := func(v reflect.Value) (reflect.Value, bool) {
if v.IsNil() {
return v, false
}
// Find potential resolver based on interface element type.
// If no resolver is found, the value seems to be ok and should be reported.
elem := v.Elem()
resolver := makeResolveNonEmptyValue(elem.Type())
if resolver == nil {
return v, true // report, as v does not seem special
}
return resolver(v.Elem())
}
var resolvers []resolver
for {
switch st.Kind() {
case reflect.Ptr:
var r resolver
st, r = makeResolvePointers(st)
resolvers = append(resolvers, r)
continue
case reflect.Interface:
resolvers = append(resolvers, resolveInterfaceLazy)
case reflect.Map, reflect.String, reflect.Slice, reflect.Array:
resolvers = append(resolvers, resolveBySize)
default:
if implementsIsZeroer(st) {
resolvers = append(resolvers, resolveIsZeroer)
} else if implementsPtrIsZeroer(st) {
resolvers = append(resolvers, resolveIsZeroerPtr)
}
}
break
}
if len(resolvers) == 0 {
return nil
}
if len(resolvers) == 1 {
return resolvers[0]
}
return func(v reflect.Value) (reflect.Value, bool) {
for _, r := range resolvers {
var ok bool
if v, ok = r(v); !ok {
return v, ok
}
}
return v, true
}
}
func makeResolvePointers(st reflect.Type) (reflect.Type, func(reflect.Value) (reflect.Value, bool)) {
N, bt := baseType(st)
return bt, func(v reflect.Value) (reflect.Value, bool) {
for i := 0; i < N; i++ {
if v.IsNil() {
return v, false
}
v = v.Elem()
}
return v, true
}
}
func getReflectFoldSlice(c *foldContext, t reflect.Type) (reFoldFn, error) {
elemVisitor, err := getReflectFold(c, t.Elem())
if err != nil {
return nil, err
}
return func(C *foldContext, rv reflect.Value) error {
count := rv.Len()
if err := C.OnArrayStart(count, structform.AnyType); err != nil {
return err
}
for i := 0; i < count; i++ {
if err := elemVisitor(C, rv.Index(i)); err != nil {
return err
}
}
return C.OnArrayFinished()
}, nil
}
/*
// TODO: create visitors casting the actual values via reflection instead of
// golang type conversion:
func getReflectFoldPrimitive(t reflect.Type) reFoldFn {
switch t.Kind() {
case reflect.Bool:
return reFoldBool
case reflect.Int:
return reFoldInt
case reflect.Int8:
return reFoldInt8
case reflect.Int16:
return reFoldInt16
case reflect.Int32:
return reFoldInt32
case reflect.Int64:
return reFoldInt64
case reflect.Uint:
return reFoldUint
case reflect.Uint8:
return reFoldUint8
case reflect.Uint16:
return reFoldUint16
case reflect.Uint32:
return reFoldUint32
case reflect.Uint64:
return reFoldUint64
case reflect.Float32:
return reFoldFloat32
case reflect.Float64:
return reFoldFloat64
case reflect.String:
return reFoldString
case reflect.Slice:
switch t.Elem().Kind() {
case reflect.Interface:
return reFoldArrAny
case reflect.Bool:
return reFoldArrBool
case reflect.Int:
return reFoldArrInt
case reflect.Int8:
return reFoldArrInt8
case reflect.Int16:
return reFoldArrInt16
case reflect.Int32:
return reFoldArrInt32
case reflect.Int64:
return reFoldArrInt64
case reflect.Uint:
return reFoldArrUint
case reflect.Uint8:
return reFoldArrUint8
case reflect.Uint16:
return reFoldArrUint16
case reflect.Uint32:
return reFoldArrUint32
case reflect.Uint64:
return reFoldArrUint64
case reflect.Float32:
return reFoldArrFloat32
case reflect.Float64:
return reFoldArrFloat64
case reflect.String:
return reFoldArrString
}
case reflect.Map:
if t.Key().Kind() != reflect.String {
return nil
}
switch t.Elem().Kind() {
case reflect.Interface:
return reflectMapAny
case reflect.Bool:
return reFoldMapBool
case reflect.Int:
return reFoldMapInt
case reflect.Int8:
return reFoldMapInt8
case reflect.Int16:
return reFoldMapInt16
case reflect.Int32:
return reFoldMapInt32
case reflect.Int64:
return reFoldMapInt64
case reflect.Uint:
return reFoldMapUint
case reflect.Uint8:
return reFoldMapUint8
case reflect.Uint16:
return reFoldMapUint16
case reflect.Uint32:
return reFoldMapUint32
case reflect.Uint64:
return reFoldMapUint64
case reflect.Float32:
return reFoldMapFloat32
case reflect.Float64:
return reFoldMapFloat64
case reflect.String:
return reFoldMapString
}
}
return nil
}
*/
func foldAnyReflect(C *foldContext, v reflect.Value) error {
f, err := getReflectFold(C, v.Type())
if err != nil {
return err
}
return f(C, v)
}
func newTypeFoldRegistry() *typeFoldRegistry {
return &typeFoldRegistry{m: map[typeFoldKey]reFoldFn{}}
}
func (r *typeFoldRegistry) find(t reflect.Type) reFoldFn {
// r.mu.RLock()
// defer r.mu.RUnlock()
return r.m[typeFoldKey{ty: t, inline: false}]
}
func (r *typeFoldRegistry) findInline(t reflect.Type) reFoldFn {
// r.mu.RLock()
// defer r.mu.RUnlock()
return r.m[typeFoldKey{ty: t, inline: true}]
}
func (r *typeFoldRegistry) set(t reflect.Type, f reFoldFn) {
// r.mu.Lock()
// defer r.mu.Unlock()
r.m[typeFoldKey{ty: t, inline: false}] = f
}
func (r *typeFoldRegistry) setInline(t reflect.Type, f reFoldFn) {
// r.mu.Lock()
// defer r.mu.Unlock()
r.m[typeFoldKey{ty: t, inline: true}] = f
}
func liftFold(sample interface{}, fn foldFn) reFoldFn {
t := reflect.TypeOf(sample)
return func(C *foldContext, v reflect.Value) error {
if v.Type().Name() != "" {
v = v.Convert(t)
}
return fn(C, v.Interface())
}
}
func baseType(t reflect.Type) (int, reflect.Type) {
i := 0
for t.Kind() == reflect.Ptr {
t = t.Elem()
i++
}
return i, t
}