gotype/unfold.go (238 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"
"unsafe"
structform "github.com/elastic/go-structform"
)
type Unfolder struct {
unfoldCtx
}
type unfoldCtx struct {
unfolder unfolderStack
ptr ptrStack
userReg map[reflect.Type]reflUnfolder
reg *typeUnfoldRegistry
value reflectValueStack
key keyStack
keyCache symbolCache
opts options
valueBuffer unfoldBuf
baseType structformTypeStack
idx idxStack
}
type unfoldBuf struct {
arrays [][]byte
mapPrimitive []map[string]byte
mapAny []map[string]interface{}
}
type ptrUnfolder interface {
initState(*unfoldCtx, unsafe.Pointer)
}
type reflUnfolder interface {
initState(*unfoldCtx, reflect.Value)
}
type unfolder interface {
// primitives
OnNil(*unfoldCtx) error
OnBool(*unfoldCtx, bool) error
OnString(*unfoldCtx, string) error
OnStringRef(*unfoldCtx, []byte) error
OnInt8(*unfoldCtx, int8) error
OnInt16(*unfoldCtx, int16) error
OnInt32(*unfoldCtx, int32) error
OnInt64(*unfoldCtx, int64) error
OnInt(*unfoldCtx, int) error
OnByte(*unfoldCtx, byte) error
OnUint8(*unfoldCtx, uint8) error
OnUint16(*unfoldCtx, uint16) error
OnUint32(*unfoldCtx, uint32) error
OnUint64(*unfoldCtx, uint64) error
OnUint(*unfoldCtx, uint) error
OnFloat32(*unfoldCtx, float32) error
OnFloat64(*unfoldCtx, float64) error
// array types
OnArrayStart(*unfoldCtx, int, structform.BaseType) error
OnArrayFinished(*unfoldCtx) error
OnChildArrayDone(*unfoldCtx) error
// object types
OnObjectStart(*unfoldCtx, int, structform.BaseType) error
OnObjectFinished(*unfoldCtx) error
OnKey(*unfoldCtx, string) error
OnKeyRef(*unfoldCtx, []byte) error
OnChildObjectDone(*unfoldCtx) error
}
type typeUnfoldRegistry struct {
m map[reflect.Type]reflUnfolder
}
func NewUnfolder(to interface{}, opts ...UnfoldOption) (*Unfolder, error) {
O, err := applyUnfoldOpts(opts)
if err != nil {
return nil, err
}
u := &Unfolder{}
u.opts = options{tag: "struct"}
u.unfolder.init(&unfolderNoTarget{})
u.value.init(reflect.Value{})
u.ptr.init()
u.key.init()
u.idx.init()
u.baseType.init()
u.valueBuffer.init()
u.reg = newTypeUnfoldRegistry()
if O.unfoldFns != nil {
u.userReg = map[reflect.Type]reflUnfolder{}
for typ, unfolder := range O.unfoldFns {
u.userReg[typ] = unfolder
u.userReg[typ.Elem()] = unfolder // add non-pointer value for arrays/maps and other structs
}
}
// TODO: make allocation buffer size configurable
// u.buf.init(1024)
if to != nil {
err := u.SetTarget(to)
if err != nil {
return nil, err
}
}
return u, nil
}
func (u *Unfolder) EnableKeyCache(max int) {
u.keyCache.init(max)
}
// Reset reinitializes the unfolder and removes all references to the target
// object. Use Reset if the unfolder is re-used and the target changed.
// References to the target can prevent the garbage collector from collecting
// the target after processing. Use Reset to set the target to `nil`.
// SetTarget must be called after Reset and before another Unfold operation.
func (u *Unfolder) Reset() {
u.SetTarget(nil)
}
func (u *Unfolder) SetTarget(to interface{}) error {
ctx := &u.unfoldCtx
if to == nil {
// reset internal states on nil
u.unfolder.init(&unfolderNoTarget{})
u.value.init(reflect.Value{})
u.ptr.init()
u.key.init()
u.idx.init()
u.baseType.init()
u.valueBuffer.reset()
return nil
}
if ptr, u := lookupGoTypeUnfolder(to); u != nil {
u.initState(ctx, ptr)
return nil
}
t := reflect.TypeOf(to)
if t.Kind() != reflect.Ptr {
return errRequiresPointer
}
ru, err := lookupReflUnfolder(&u.unfoldCtx, t, true)
if err != nil {
return err
}
if ru != nil {
ru.initState(ctx, reflect.ValueOf(to))
return nil
}
return errUnsupported
}
func (u *unfoldCtx) OnObjectStart(len int, baseType structform.BaseType) error {
return u.unfolder.current.OnObjectStart(u, len, baseType)
}
func (u *unfoldCtx) OnObjectFinished() error {
lBefore := len(u.unfolder.stack) + 1
if err := u.unfolder.current.OnObjectFinished(u); err != nil {
return err
}
lAfter := len(u.unfolder.stack) + 1
if old := u.unfolder.current; lAfter > 1 && lBefore > lAfter {
return old.OnChildObjectDone(u)
}
return nil
}
func (u *unfoldCtx) OnKey(s string) error {
return u.unfolder.current.OnKey(u, s)
}
func (u *unfoldCtx) OnKeyRef(s []byte) error {
return u.unfolder.current.OnKeyRef(u, s)
}
func (u *unfoldCtx) OnArrayStart(len int, baseType structform.BaseType) error {
return u.unfolder.current.OnArrayStart(u, len, baseType)
}
func (u *unfoldCtx) OnArrayFinished() error {
lBefore := len(u.unfolder.stack) + 1
if err := u.unfolder.current.OnArrayFinished(u); err != nil {
return err
}
lAfter := len(u.unfolder.stack) + 1
if old := u.unfolder.current; lAfter > 1 && lBefore > lAfter {
return old.OnChildArrayDone(u)
}
return nil
}
func (u *unfoldCtx) OnNil() error {
return u.unfolder.current.OnNil(u)
}
func (u *unfoldCtx) OnBool(b bool) error {
return u.unfolder.current.OnBool(u, b)
}
func (u *unfoldCtx) OnString(s string) error {
return u.unfolder.current.OnString(u, s)
}
func (u *unfoldCtx) OnStringRef(s []byte) error {
return u.unfolder.current.OnStringRef(u, s)
}
func (u *unfoldCtx) OnInt8(i int8) error {
return u.unfolder.current.OnInt8(u, i)
}
func (u *unfoldCtx) OnInt16(i int16) error {
return u.unfolder.current.OnInt16(u, i)
}
func (u *unfoldCtx) OnInt32(i int32) error {
return u.unfolder.current.OnInt32(u, i)
}
func (u *unfoldCtx) OnInt64(i int64) error {
return u.unfolder.current.OnInt64(u, i)
}
func (u *unfoldCtx) OnInt(i int) error {
return u.unfolder.current.OnInt(u, i)
}
func (u *unfoldCtx) OnByte(b byte) error {
return u.unfolder.current.OnByte(u, b)
}
func (u *unfoldCtx) OnUint8(v uint8) error {
return u.unfolder.current.OnUint8(u, v)
}
func (u *unfoldCtx) OnUint16(v uint16) error {
return u.unfolder.current.OnUint16(u, v)
}
func (u *unfoldCtx) OnUint32(v uint32) error {
return u.unfolder.current.OnUint32(u, v)
}
func (u *unfoldCtx) OnUint64(v uint64) error {
return u.unfolder.current.OnUint64(u, v)
}
func (u *unfoldCtx) OnUint(v uint) error {
return u.unfolder.current.OnUint(u, v)
}
func (u *unfoldCtx) OnFloat32(f float32) error {
return u.unfolder.current.OnFloat32(u, f)
}
func (u *unfoldCtx) OnFloat64(f float64) error {
return u.unfolder.current.OnFloat64(u, f)
}
func newTypeUnfoldRegistry() *typeUnfoldRegistry {
return &typeUnfoldRegistry{m: map[reflect.Type]reflUnfolder{}}
}
func (r *typeUnfoldRegistry) find(t reflect.Type) reflUnfolder {
return r.m[t]
}
func (r *typeUnfoldRegistry) set(t reflect.Type, f reflUnfolder) {
r.m[t] = f
}
func makeUnfoldBuf() unfoldBuf {
return unfoldBuf{
arrays: make([][]byte, 0, 4),
mapPrimitive: make([]map[string]byte, 0, 1),
mapAny: make([]map[string]interface{}, 0, 4),
}
}
func (u *unfoldBuf) init() {
*u = makeUnfoldBuf()
}
func (u *unfoldBuf) reset() {
u.arrays = u.arrays[:0]
u.mapPrimitive = u.mapPrimitive[:0]
u.mapAny = u.mapAny[:0]
}