Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
4 contributors

Users who have contributed to this file

@bluele @noahgoldman @ismith @cforbes
414 lines (353 sloc) 9.21 KB
package factory
import (
"context"
"errors"
"reflect"
"strconv"
"sync/atomic"
)
var (
TagName = "factory"
emptyValue = reflect.Value{}
)
type Factory struct {
model interface{}
numField int
rt reflect.Type
rv *reflect.Value
attrGens []*attrGenerator
nameIndexMap map[string]int // pair for attribute name and field index.
isPtr bool
onCreate func(Args) error
}
type Args interface {
Instance() interface{}
Parent() Args
Context() context.Context
pipeline(int) *pipeline
}
type argsStruct struct {
ctx context.Context
rv *reflect.Value
pl *pipeline
}
// Instance returns a object to which the generator declared just before is applied
func (args *argsStruct) Instance() interface{} {
return args.rv.Interface()
}
// Parent returns a parent argument if current factory is a subfactory of parent
func (args *argsStruct) Parent() Args {
if args.pl == nil {
return nil
}
return args.pl.parent
}
func (args *argsStruct) pipeline(num int) *pipeline {
if args.pl == nil {
return newPipeline(num)
}
return args.pl
}
func (args *argsStruct) Context() context.Context {
return args.ctx
}
func (args *argsStruct) UpdateContext(ctx context.Context) {
args.ctx = ctx
}
type Stacks []*int64
func (st *Stacks) Size(idx int) int64 {
return *(*st)[idx]
}
// Set method is not goroutine safe.
func (st *Stacks) Set(idx, val int) {
var ini int64 = 0
(*st)[idx] = &ini
atomic.StoreInt64((*st)[idx], int64(val))
}
func (st *Stacks) Push(idx, delta int) {
atomic.AddInt64((*st)[idx], int64(delta))
}
func (st *Stacks) Pop(idx, delta int) {
atomic.AddInt64((*st)[idx], -int64(delta))
}
func (st *Stacks) Next(idx int) bool {
st.Pop(idx, 1)
return *(*st)[idx] >= 0
}
func (st *Stacks) Has(idx int) bool {
return (*st)[idx] != nil
}
type pipeline struct {
stacks Stacks
parent Args
}
func newPipeline(size int) *pipeline {
return &pipeline{stacks: make(Stacks, size)}
}
func (pl *pipeline) Next(args Args) *pipeline {
npl := &pipeline{}
npl.parent = args
npl.stacks = make(Stacks, len(pl.stacks))
for i, sptr := range pl.stacks {
if sptr != nil {
stack := *sptr
npl.stacks[i] = &stack
}
}
return npl
}
// NewFactory returns a new factory for specified model class
// Each generator is applied in the order in which they are declared
func NewFactory(model interface{}) *Factory {
fa := &Factory{}
fa.model = model
fa.nameIndexMap = make(map[string]int)
fa.init()
return fa
}
type attrGenerator struct {
genFunc func(Args) (interface{}, error)
key string
value interface{}
isNil bool
}
func (fa *Factory) init() {
rt := reflect.TypeOf(fa.model)
rv := reflect.ValueOf(fa.model)
fa.isPtr = rt.Kind() == reflect.Ptr
if fa.isPtr {
rt = rt.Elem()
rv = rv.Elem()
}
fa.numField = rv.NumField()
for i := 0; i < fa.numField; i++ {
tf := rt.Field(i)
vf := rv.Field(i)
ag := &attrGenerator{}
if !vf.CanSet() || (tf.Type.Kind() == reflect.Ptr && vf.IsNil()) {
ag.isNil = true
} else {
ag.value = vf.Interface()
}
attrName := getAttrName(tf, TagName)
ag.key = attrName
fa.nameIndexMap[attrName] = i
fa.attrGens = append(fa.attrGens, ag)
}
fa.rt = rt
fa.rv = &rv
}
func (fa *Factory) modelName() string {
return fa.rt.Name()
}
func (fa *Factory) Attr(name string, gen func(Args) (interface{}, error)) *Factory {
idx := fa.checkIdx(name)
fa.attrGens[idx].genFunc = gen
return fa
}
func (fa *Factory) SeqInt(name string, gen func(int) (interface{}, error)) *Factory {
idx := fa.checkIdx(name)
var seq int64 = 0
fa.attrGens[idx].genFunc = func(args Args) (interface{}, error) {
new := atomic.AddInt64(&seq, 1)
return gen(int(new))
}
return fa
}
func (fa *Factory) SeqString(name string, gen func(string) (interface{}, error)) *Factory {
idx := fa.checkIdx(name)
var seq int64 = 0
fa.attrGens[idx].genFunc = func(args Args) (interface{}, error) {
new := atomic.AddInt64(&seq, 1)
return gen(strconv.FormatInt(new, 10))
}
return fa
}
func (fa *Factory) SubFactory(name string, sub *Factory) *Factory {
idx := fa.checkIdx(name)
fa.attrGens[idx].genFunc = func(args Args) (interface{}, error) {
pipeline := args.pipeline(fa.numField)
ret, err := sub.create(args.Context(), nil, pipeline.Next(args))
if err != nil {
return nil, err
}
return ret, nil
}
return fa
}
func (fa *Factory) SubSliceFactory(name string, sub *Factory, getSize func() int) *Factory {
idx := fa.checkIdx(name)
tp := fa.rt.Field(idx).Type
fa.attrGens[idx].genFunc = func(args Args) (interface{}, error) {
size := getSize()
pipeline := args.pipeline(fa.numField)
sv := reflect.MakeSlice(tp, size, size)
for i := 0; i < size; i++ {
ret, err := sub.create(args.Context(), nil, pipeline.Next(args))
if err != nil {
return nil, err
}
sv.Index(i).Set(reflect.ValueOf(ret))
}
return sv.Interface(), nil
}
return fa
}
func (fa *Factory) SubRecursiveFactory(name string, sub *Factory, getLimit func() int) *Factory {
idx := fa.checkIdx(name)
fa.attrGens[idx].genFunc = func(args Args) (interface{}, error) {
pl := args.pipeline(fa.numField)
if !pl.stacks.Has(idx) {
pl.stacks.Set(idx, getLimit())
}
if pl.stacks.Next(idx) {
ret, err := sub.create(args.Context(), nil, pl.Next(args))
if err != nil {
return nil, err
}
return ret, nil
}
return nil, nil
}
return fa
}
func (fa *Factory) SubRecursiveSliceFactory(name string, sub *Factory, getSize, getLimit func() int) *Factory {
idx := fa.checkIdx(name)
tp := fa.rt.Field(idx).Type
fa.attrGens[idx].genFunc = func(args Args) (interface{}, error) {
pl := args.pipeline(fa.numField)
if !pl.stacks.Has(idx) {
pl.stacks.Set(idx, getLimit())
}
if pl.stacks.Next(idx) {
size := getSize()
sv := reflect.MakeSlice(tp, size, size)
for i := 0; i < size; i++ {
ret, err := sub.create(args.Context(), nil, pl.Next(args))
if err != nil {
return nil, err
}
sv.Index(i).Set(reflect.ValueOf(ret))
}
return sv.Interface(), nil
}
return nil, nil
}
return fa
}
// OnCreate registers a callback on object creation.
// If callback function returns error, object creation is failed.
func (fa *Factory) OnCreate(cb func(Args) error) *Factory {
fa.onCreate = cb
return fa
}
func (fa *Factory) checkIdx(name string) int {
idx, ok := fa.nameIndexMap[name]
if !ok {
panic("No such attribute name: " + name)
}
return idx
}
func (fa *Factory) Create() (interface{}, error) {
return fa.CreateWithOption(nil)
}
func (fa *Factory) CreateWithOption(opt map[string]interface{}) (interface{}, error) {
return fa.create(context.Background(), opt, nil)
}
func (fa *Factory) CreateWithContext(ctx context.Context) (interface{}, error) {
return fa.create(ctx, nil, nil)
}
func (fa *Factory) CreateWithContextAndOption(ctx context.Context, opt map[string]interface{}) (interface{}, error) {
return fa.create(ctx, opt, nil)
}
func (fa *Factory) MustCreate() interface{} {
return fa.MustCreateWithOption(nil)
}
func (fa *Factory) MustCreateWithOption(opt map[string]interface{}) interface{} {
inst, err := fa.CreateWithOption(opt)
if err != nil {
panic(err)
}
return inst
}
/*
Bind values of a new objects to a pointer to struct.
ptr: a pointer to struct
*/
func (fa *Factory) Construct(ptr interface{}) error {
return fa.ConstructWithOption(ptr, nil)
}
/*
Bind values of a new objects to a pointer to struct with option.
ptr: a pointer to struct
opt: attibute values
*/
func (fa *Factory) ConstructWithOption(ptr interface{}, opt map[string]interface{}) error {
return fa.ConstructWithContextAndOption(context.Background(), ptr, opt)
}
/*
Bind values of a new objects to a pointer to struct with context and option.
ctx: context object
ptr: a pointer to struct
opt: attibute values
*/
func (fa *Factory) ConstructWithContextAndOption(ctx context.Context, ptr interface{}, opt map[string]interface{}) error {
pt := reflect.TypeOf(ptr)
if pt.Kind() != reflect.Ptr {
return errors.New("ptr should be pointer type.")
}
pt = pt.Elem()
if pt.Name() != fa.modelName() {
return errors.New("ptr type should be " + fa.modelName())
}
inst := reflect.ValueOf(ptr).Elem()
_, err := fa.build(ctx, &inst, pt, opt, nil)
return err
}
func (fa *Factory) build(ctx context.Context, inst *reflect.Value, tp reflect.Type, opt map[string]interface{}, pl *pipeline) (interface{}, error) {
args := &argsStruct{}
args.pl = pl
args.ctx = ctx
if fa.isPtr {
addr := (*inst).Addr()
args.rv = &addr
} else {
args.rv = inst
}
for i := 0; i < fa.numField; i++ {
if v, ok := opt[fa.attrGens[i].key]; ok {
inst.Field(i).Set(reflect.ValueOf(v))
} else {
ag := fa.attrGens[i]
if ag.genFunc == nil {
if !ag.isNil {
inst.Field(i).Set(reflect.ValueOf(ag.value))
}
} else {
v, err := ag.genFunc(args)
if err != nil {
return nil, err
}
if v != nil {
inst.Field(i).Set(reflect.ValueOf(v))
}
}
}
}
for k, v := range opt {
setValueWithAttrPath(inst, tp, k, v)
}
if fa.onCreate != nil {
if err := fa.onCreate(args); err != nil {
return nil, err
}
}
if fa.isPtr {
return (*inst).Addr().Interface(), nil
}
return inst.Interface(), nil
}
func (fa *Factory) create(ctx context.Context, opt map[string]interface{}, pl *pipeline) (interface{}, error) {
inst := reflect.New(fa.rt).Elem()
return fa.build(ctx, &inst, fa.rt, opt, pl)
}
You can’t perform that action at this time.