gotype/unfold_struct.go (144 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 (
"fmt"
"reflect"
"strings"
"unicode"
"unicode/utf8"
"unsafe"
structform "github.com/elastic/go-structform"
)
type unfolderStruct struct {
unfolderErrExpectKey
fields map[string]fieldUnfolder
}
type unfolderStructStart struct {
unfolderErrObjectStart
}
type fieldUnfolder struct {
offset uintptr
initState func(ctx *unfoldCtx, sp unsafe.Pointer)
}
var (
_singletonUnfolderStructStart = &unfolderStructStart{}
_ignoredField = &fieldUnfolder{
initState: _singletonUnfoldIgnorePtr.initState,
}
)
func createUnfolderReflStruct(ctx *unfoldCtx, t reflect.Type) (*unfolderStruct, error) {
// assume t is pointer to struct
t = t.Elem()
fields, err := fieldUnfolders(ctx, t)
if err != nil {
return nil, err
}
u := &unfolderStruct{fields: fields}
return u, nil
}
func fieldUnfolders(ctx *unfoldCtx, t reflect.Type) (map[string]fieldUnfolder, error) {
count := t.NumField()
fields := map[string]fieldUnfolder{}
for i := 0; i < count; i++ {
st := t.Field(i)
name := st.Name
rune, _ := utf8.DecodeRuneInString(name)
if !unicode.IsUpper(rune) {
continue
}
tagName, tagOpts := parseTags(st.Tag.Get(ctx.opts.tag))
if tagOpts.omit {
continue
}
if tagOpts.squash {
if st.Type.Kind() != reflect.Struct {
return nil, errSquashNeedObject
}
sub, err := fieldUnfolders(ctx, st.Type)
if err != nil {
return nil, err
}
for name, fu := range sub {
fu.offset += st.Offset
if _, exists := fields[name]; exists {
return nil, fmt.Errorf("duplicate field name %v", name)
}
fields[name] = fu
}
} else {
if tagName != "" {
name = tagName
} else {
name = strings.ToLower(name)
}
if _, exists := fields[name]; exists {
return nil, fmt.Errorf("duplicate field name %v", name)
}
fu, err := makeFieldUnfolder(ctx, st)
if err != nil {
return nil, err
}
fields[name] = fu
}
}
return fields, nil
}
func makeFieldUnfolder(ctx *unfoldCtx, st reflect.StructField) (fieldUnfolder, error) {
fu := fieldUnfolder{offset: st.Offset}
targetType := reflect.PtrTo(st.Type)
if uu := lookupReflUser(ctx, targetType); uu != nil {
fu.initState = wrapReflUnfolder(st.Type, uu)
} else if targetType.Implements(tExpander) {
fu.initState = wrapReflUnfolder(st.Type, newExpanderInit())
} else if pu := lookupGoPtrUnfolder(st.Type); pu != nil {
fu.initState = pu.initState
} else {
ru, err := lookupReflUnfolder(ctx, targetType, false)
if err != nil {
return fu, err
}
if su, ok := ru.(*unfolderStruct); ok {
fu.initState = su.initStatePtr
} else {
fu.initState = wrapReflUnfolder(st.Type, ru)
}
}
return fu, nil
}
func wrapReflUnfolder(t reflect.Type, ru reflUnfolder) func(*unfoldCtx, unsafe.Pointer) {
return func(ctx *unfoldCtx, ptr unsafe.Pointer) {
v := reflect.NewAt(t, ptr)
ru.initState(ctx, v)
}
}
func (u *unfolderStruct) initState(ctx *unfoldCtx, v reflect.Value) {
u.initStatePtr(ctx, unsafe.Pointer(v.Pointer()))
}
func (u *unfolderStruct) initStatePtr(ctx *unfoldCtx, ptr unsafe.Pointer) {
ctx.ptr.push(ptr)
ctx.unfolder.push(u)
ctx.unfolder.push(_singletonUnfolderStructStart)
}
func (u *unfolderStructStart) OnObjectStart(ctx *unfoldCtx, l int, bt structform.BaseType) error {
ctx.unfolder.pop()
return nil
}
func (u *unfolderStruct) OnObjectFinished(ctx *unfoldCtx) error {
ctx.unfolder.pop()
ctx.ptr.pop()
return nil
}
func (u *unfolderStruct) OnChildObjectDone(ctx *unfoldCtx) error { return nil }
func (u *unfolderStruct) OnChildArrayDone(ctx *unfoldCtx) error { return nil }
func (u *unfolderStruct) OnKeyRef(ctx *unfoldCtx, key []byte) error {
return u.OnKey(ctx, bytes2Str(key))
}
func (u *unfolderStruct) OnKey(ctx *unfoldCtx, key string) error {
field, exists := u.fields[key]
if !exists {
_ignoredField.initState(ctx, nil)
return nil
}
structPtr := ctx.ptr.current
fieldPtr := unsafe.Pointer(uintptr(structPtr) + field.offset)
field.initState(ctx, fieldPtr)
return nil
}