From 0ad06cd2e4c86f888828f613db22b94f2013fe96 Mon Sep 17 00:00:00 2001 From: crusader Date: Thu, 23 Aug 2018 17:06:25 +0900 Subject: [PATCH] project initialized --- .gitignore | 69 ++++++ .vscode/launch.json | 32 +++ .vscode/settings.json | 3 + Gopkg.lock | 21 ++ Gopkg.toml | 34 +++ annotation/inject.go | 20 ++ annotation/injectable.go | 31 +++ annotation/resource.go | 20 ++ constants.go | 17 ++ definition.go | 18 ++ di.go | 1 + registry.go | 266 +++++++++++++++++++++ registry/definition.go | 135 +++++++++++ registry/registry.go | 411 +++++++++++++++++++++++++++++++++ registry/registry_test.go | 68 ++++++ registry_test.go | 472 ++++++++++++++++++++++++++++++++++++++ 16 files changed, 1618 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml create mode 100644 annotation/inject.go create mode 100644 annotation/injectable.go create mode 100644 annotation/resource.go create mode 100644 constants.go create mode 100644 definition.go create mode 100644 di.go create mode 100644 registry.go create mode 100644 registry/definition.go create mode 100644 registry/registry.go create mode 100644 registry/registry_test.go create mode 100644 registry_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac65c48 --- /dev/null +++ b/.gitignore @@ -0,0 +1,69 @@ +# Created by .ignore support plugin (hsz.mobi) +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +### Go template +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ +.idea/ +*.iml + +vendor/ +glide.lock +.DS_Store +dist/ +debug +debug.test diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2ca2b1d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,32 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 2345, + "host": "127.0.0.1", + "program": "${workspaceRoot}/main.go", + "env": {}, + "args": [], + "showLog": true + }, + { + "name": "File Debug", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 2345, + "host": "127.0.0.1", + "program": "${fileDirname}", + "env": {}, + "args": [], + "showLog": true + } + + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..20af2f6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +// Place your settings in this file to overwrite default and user settings. +{ +} \ No newline at end of file diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..5651810 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,21 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + name = "git.loafle.net/overflow/annotation-go" + packages = ["."] + revision = "9d80d778c61e1a08a46c19835e36f18cf08c1f91" + +[[projects]] + branch = "master" + name = "git.loafle.net/overflow/util-go" + packages = ["reflect"] + revision = "01cc315e25b38331c0a366d8025600ed4e297e8e" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "9d3f241b9a5d4d85e1e521342e1ad16ae4552869b317e9dd1ff9cdf255edfbb0" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..6e9d634 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,34 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + branch = "master" + name = "git.loafle.net/overflow/util-go" + +[prune] + go-tests = true + unused-packages = true diff --git a/annotation/inject.go b/annotation/inject.go new file mode 100644 index 0000000..85b7f89 --- /dev/null +++ b/annotation/inject.go @@ -0,0 +1,20 @@ +package annotation + +// @Inject(name? string) + +import ( + "reflect" + + oa "git.loafle.net/overflow/annotation-go" +) + +func init() { + oa.Register(InjectAnnotationType) +} + +var InjectAnnotationType = reflect.TypeOf((*InjectAnnotation)(nil)) + +type InjectAnnotation struct { + oa.Annotation `@name:"@Inject"` + Name string `json:"name" @default:""` +} diff --git a/annotation/injectable.go b/annotation/injectable.go new file mode 100644 index 0000000..063bcac --- /dev/null +++ b/annotation/injectable.go @@ -0,0 +1,31 @@ +package annotation + +// @Inject(name? string) + +import ( + "reflect" + + oa "git.loafle.net/overflow/annotation-go" +) + +func init() { + oa.Register(InjectableAnnotationType) +} + +var InjectableAnnotationType = reflect.TypeOf((*InjectableAnnotation)(nil)) + +type InjectableAnnotation struct { + oa.Annotation `@name:"@Injectable"` + Name string `json:"name" @default:""` + InitMethod string `json:"initMethod"` + DestroyMethod string `json:"destroyMethod"` + Scope ScopeType `json:"scope"` +} + +type ScopeType string + +const ( + ScopeTypeDefault ScopeType = "Default" // == ScopeTypeSingleton + ScopeTypeSingleton ScopeType = "Singleton" + ScopeTypeTransiant ScopeType = "Transiant" +) diff --git a/annotation/resource.go b/annotation/resource.go new file mode 100644 index 0000000..01749b7 --- /dev/null +++ b/annotation/resource.go @@ -0,0 +1,20 @@ +package annotation + +// @Resource(name? string) + +import ( + "reflect" + + oa "git.loafle.net/overflow/annotation-go" +) + +func init() { + oa.Register(ResourceAnnotationType) +} + +var ResourceAnnotationType = reflect.TypeOf((*ResourceAnnotation)(nil)) + +type ResourceAnnotation struct { + oa.Annotation `@name:"@Resource"` + Name string `json:"name" @default:""` +} diff --git a/constants.go b/constants.go new file mode 100644 index 0000000..5f47f9c --- /dev/null +++ b/constants.go @@ -0,0 +1,17 @@ +package di + +// `annotation:"@Inject(name? string)"` +// field 에 적용 +// 1. 타입으로 매칭 없으면 에러 2. 여러개의 타입이 검색되면 그 중에 name으로 매칭 + +// `annotation:"@Resource(name? string)"` +// field 에 적용 +// 1. 이름으로 매칭 2. 타입으로 매칭 +// 이름이 지정되지 않으면 field 이름으로 먼저 찾고 없으면 타입으로 매칭 + +// @Component +// Component 등록 시에 파라미터로 제공 +// names []string +// initMethod string +// destroyMethod string +// scope enum singleton, transiant diff --git a/definition.go b/definition.go new file mode 100644 index 0000000..f467ef5 --- /dev/null +++ b/definition.go @@ -0,0 +1,18 @@ +package di + +import ( + "fmt" + "reflect" +) + +type TypeDefinition struct { + FullName string + PkgName string + TypeName string + Type reflect.Type + RealType reflect.Type +} + +func FullName(pkgName, typeName string) string { + return fmt.Sprintf("%s/%s", pkgName, typeName) +} diff --git a/di.go b/di.go new file mode 100644 index 0000000..4e7baf6 --- /dev/null +++ b/di.go @@ -0,0 +1 @@ +package di diff --git a/registry.go b/registry.go new file mode 100644 index 0000000..32e4de5 --- /dev/null +++ b/registry.go @@ -0,0 +1,266 @@ +package di + +import ( + "fmt" + "log" + "reflect" + "strings" + + oa "git.loafle.net/overflow/annotation-go" + "git.loafle.net/overflow/di-go/annotation" + our "git.loafle.net/overflow/util-go/reflect" +) + +type Registry interface { + RegisterType(t reflect.Type) + RegisterResource(name string, resource interface{}) error + + GetInstance(t reflect.Type) (interface{}, error) + GetInstances(ts []reflect.Type) ([]interface{}, error) + GetInstanceByName(name string) (interface{}, error) + GetInstancesByAnnotationType(t reflect.Type) ([]interface{}, error) +} + +func New(parent Registry) Registry { + r := &InstanceRegistry{ + parent: parent, + definitionByType: make(map[reflect.Type]*TypeDefinition, 0), + definitionByName: make(map[string]*TypeDefinition, 0), + instanceByType: make(map[reflect.Type]interface{}, 0), + instanceByName: make(map[string]interface{}, 0), + } + if nil == r.parent { + r.parent = AppRegistry + } + return r +} + +var AppRegistry = &InstanceRegistry{ + parent: nil, + definitionByType: make(map[reflect.Type]*TypeDefinition, 0), + definitionByName: make(map[string]*TypeDefinition, 0), + instanceByType: make(map[reflect.Type]interface{}, 0), + instanceByName: make(map[string]interface{}, 0), +} + +type InstanceRegistry struct { + parent Registry + definitionByType map[reflect.Type]*TypeDefinition + definitionByName map[string]*TypeDefinition + instanceByType map[reflect.Type]interface{} + instanceByName map[string]interface{} +} + +func RegisterType(t reflect.Type) { + AppRegistry.RegisterType(t) +} +func (r *InstanceRegistry) RegisterType(t reflect.Type) { + if nil == t { + log.Panicf("t[reflect.Type] is nil") + } + if !our.IsTypeKind(t, reflect.Struct, true) { + log.Panicf("t[reflect.Type] must be specified but is %v", t) + } + + td, err := r.buildDefinition(t) + if nil != err { + log.Panicf("DI: %v", err) + } + + if _, ok := r.definitionByType[td.Type]; ok { + log.Panicf("The type[%s] of Component is exist already", td.FullName) + } + r.definitionByType[td.Type] = td + + name := td.TypeName + + a, err := oa.GetTypeAnnotation(td.Type, annotation.InjectableAnnotationType) + if nil != err { + log.Panicf("%v", err) + } + if nil != a { + ia := a.(*annotation.InjectableAnnotation) + if "" != strings.Trim(ia.Name, " ") { + name = ia.Name + } + } + + if eTD, ok := r.definitionByName[name]; ok { + log.Panicf("The name[%s] of Component is exist already type[%s]", name, eTD.FullName) + } + r.definitionByName[name] = td +} + +func RegisterResource(name string, resource interface{}) error { + return AppRegistry.RegisterResource(name, resource) +} +func (r *InstanceRegistry) RegisterResource(name string, resource interface{}) error { + if _, ok := r.instanceByName[name]; ok { + return fmt.Errorf("Resource[%s] is already exist", name) + } + r.instanceByName[name] = resource + + return nil +} + +func (r *InstanceRegistry) buildDefinition(t reflect.Type) (*TypeDefinition, error) { + if nil == t { + return nil, fmt.Errorf("t[reflect.Type] is nil") + } + + rt, pkgName, tName := our.GetTypeInfo(t) + td := &TypeDefinition{} + td.FullName = FullName(pkgName, tName) + td.PkgName = pkgName + td.TypeName = tName + td.Type = t + td.RealType = rt + + return td, nil +} + +// GetInstance returns instance of type +// t must be pointer of struct +func GetInstance(t reflect.Type) (interface{}, error) { + return AppRegistry.GetInstance(t) +} +func (r *InstanceRegistry) GetInstance(t reflect.Type) (instance interface{}, err error) { + if nil == t { + return nil, fmt.Errorf("t[reflect.Type] is nil") + } + + if reflect.Ptr != t.Kind() { + return nil, fmt.Errorf("t[reflect.Type] must be pointer of struct") + } + + if reflect.Struct != t.Elem().Kind() { + return nil, fmt.Errorf("t[reflect.Type] must be pointer of struct") + } + + var ( + td *TypeDefinition + comV interface{} + ok bool + ) + + rt, _, _ := our.GetTypeInfo(t) + if td, ok = r.definitionByType[t]; !ok { + if td, err = r.buildDefinition(t); nil != err { + return nil, fmt.Errorf("DI: %v", err) + } + } + + if comV, ok = r.instanceByType[td.RealType]; ok { + return comV, nil + } + + v := reflect.New(rt) + rv := v.Elem() + + instance = v.Interface() + r.instanceByType[td.RealType] = instance + err = nil + defer func() { + if nil != err { + instance = nil + delete(r.instanceByType, td.RealType) + } + }() + + var ( + fV interface{} + ) + + ass, err := oa.GetAllFieldAnnotations(td.Type) + if nil != err { + return nil, fmt.Errorf("%v", err) + } + if nil != ass { + for n, as := range ass { + f := rv.FieldByName(n) + + if !f.IsValid() { + err = fmt.Errorf("Field[%s] is not valid", n) + return + } + if !f.CanSet() { + err = fmt.Errorf("Field[%s] can not set", n) + return + } + + _, ok = as[annotation.InjectAnnotationType] + if ok { + if fV, err = r.GetInstance(f.Type()); nil != err { + return + } + } + + if nil != err { + return + } + f.Set(reflect.ValueOf(fV)) + } + } + + return +} + +func GetInstanceByName(name string) (interface{}, error) { + return AppRegistry.GetInstanceByName(name) +} +func (r *InstanceRegistry) GetInstanceByName(name string) (interface{}, error) { + v, ok := r.instanceByName[name] + if ok { + return v, nil + } + return nil, fmt.Errorf("Resource[%s] is not exist", name) +} + +// GetInstances returns instance of annotated +// n must be name of registered annotation +func GetInstances(ts []reflect.Type) ([]interface{}, error) { + return AppRegistry.GetInstances(ts) +} +func (r *InstanceRegistry) GetInstances(ts []reflect.Type) ([]interface{}, error) { + var ( + i interface{} + err error + ) + instances := make([]interface{}, 0) + for _, t := range ts { + if i, err = r.GetInstance(t); nil != err { + return nil, err + } + instances = append(instances, i) + } + + return instances, nil +} + +func GetInstancesByAnnotationType(at reflect.Type) ([]interface{}, error) { + return AppRegistry.GetInstancesByAnnotationType(at) +} +func (r *InstanceRegistry) GetInstancesByAnnotationType(at reflect.Type) ([]interface{}, error) { + var ( + a oa.Annotation + i interface{} + err error + ) + instances := make([]interface{}, 0) + + for _, td := range r.definitionByType { + a, err = oa.GetTypeAnnotation(td.Type, at) + if nil != err { + return nil, err + } + if nil != a { + i, err = r.GetInstance(td.Type) + if nil != err { + return nil, err + } + instances = append(instances, i) + } + } + + return instances, nil +} diff --git a/registry/definition.go b/registry/definition.go new file mode 100644 index 0000000..0d23d94 --- /dev/null +++ b/registry/definition.go @@ -0,0 +1,135 @@ +package registry + +import ( + "fmt" + "reflect" + + cda "git.loafle.net/overflow/di-go/annotation" + cur "git.loafle.net/overflow/util-go/reflect" +) + +type TypeDefinition struct { + FullName string + PkgName string + TypeName string + Type reflect.Type + RealType reflect.Type + + TypeAnnotations map[reflect.Type]cda.Annotation + MethodAnnotations map[string]map[reflect.Type]cda.Annotation + Fields []*FieldDefinition +} + +func (td *TypeDefinition) GetTypeAnnotationByType(at reflect.Type, includeEmbedding bool) cda.Annotation { + if nil == td.TypeAnnotations { + return nil + } + + if !includeEmbedding { + return td.TypeAnnotations[at] + } + + for _, v := range td.TypeAnnotations { + if at == reflect.TypeOf(v) { + return v + } + + if includeEmbedding { + if checkAnnotation(reflect.TypeOf(v), at) { + return v + } + } + } + + return nil +} + +func (td *TypeDefinition) GetMethodAnnotationByType(at reflect.Type, methodName string) cda.Annotation { + if nil == td.MethodAnnotations { + return nil + } + + ms, ok := td.MethodAnnotations[methodName] + if !ok { + return nil + } + + return ms[at] +} + +func (td *TypeDefinition) GetMethodAnnotationsByType(at reflect.Type) map[string]cda.Annotation { + if nil == td.MethodAnnotations { + return nil + } + + mas := make(map[string]cda.Annotation) + for k, v := range td.MethodAnnotations { + a, ok := v[at] + if !ok { + continue + } + mas[k] = a + } + + return mas +} + +func checkAnnotation(t reflect.Type, st reflect.Type) bool { + rt, _, _ := cur.GetTypeInfo(t) + if reflect.Struct != rt.Kind() { + return false + } + + for i := 0; i < rt.NumField(); i++ { + f := rt.Field(i) + + if f.Anonymous { + if f.Type == st { + return true + } + if checkAnnotation(f.Type, st) { + return true + } + } + } + + return false +} + +type FieldDefinition struct { + FieldName string + PkgName string + TypeName string + Type reflect.Type + RealType reflect.Type + + Annotations map[reflect.Type]cda.Annotation +} + +func (fd *FieldDefinition) GetAnnotationByType(at reflect.Type, includeEmbedding bool) cda.Annotation { + if nil == fd.Annotations { + return nil + } + + if !includeEmbedding { + return fd.Annotations[at] + } + + for _, v := range fd.Annotations { + if at == reflect.TypeOf(v) { + return v + } + + if includeEmbedding { + if checkAnnotation(reflect.TypeOf(v), at) { + return v + } + } + } + + return nil +} + +func FullName(pkgName, typeName string) string { + return fmt.Sprintf("%s/%s", pkgName, typeName) +} diff --git a/registry/registry.go b/registry/registry.go new file mode 100644 index 0000000..5416eda --- /dev/null +++ b/registry/registry.go @@ -0,0 +1,411 @@ +package registry + +import ( + "fmt" + "log" + "reflect" + "runtime" + "strings" + + cda "git.loafle.net/overflow/di-go/annotation" + cdia "git.loafle.net/overflow/di-go/injection/annotation" + cur "git.loafle.net/overflow/util-go/reflect" +) + +func init() { + registry = newRegistry() +} + +var registry Registry + +type Registry interface { + RegisterType(t reflect.Type) + RegisterResource(name string, resource interface{}) error + + GetInstance(t reflect.Type) (interface{}, error) + GetInstances(ts []reflect.Type) ([]interface{}, error) + GetInstanceByName(name string) (interface{}, error) + GetInstancesByAnnotationType(t reflect.Type) ([]interface{}, error) + + GetTypeAnnotation(instanceType reflect.Type, annotationType reflect.Type) cda.Annotation + GetMethodAnnotation(instanceType reflect.Type, annotationType reflect.Type, methodName string) cda.Annotation + GetMethodAnnotations(instanceType reflect.Type, annotationType reflect.Type) map[string]cda.Annotation +} + +func newRegistry() Registry { + r := &defaultComponentRegistry{} + r.definitionByType = make(map[reflect.Type]*TypeDefinition, 0) + r.definitionByName = make(map[string]*TypeDefinition, 0) + r.instanceByType = make(map[reflect.Type]interface{}, 0) + r.resourceByName = make(map[string]interface{}, 0) + + return r +} + +type defaultComponentRegistry struct { + definitionByType map[reflect.Type]*TypeDefinition + definitionByName map[string]*TypeDefinition + instanceByType map[reflect.Type]interface{} + resourceByName map[string]interface{} +} + +func RegisterType(t reflect.Type) { + pc, _, _, ok := runtime.Caller(1) + details := runtime.FuncForPC(pc) + if ok && details != nil { + log.Printf("called from %s", details.Name()) + } + + registry.RegisterType(t) +} +func (cr *defaultComponentRegistry) RegisterType(t reflect.Type) { + if nil == t { + log.Panicf("DI: t[reflect.Type] is nil") + } + if !cur.IsTypeKind(t, reflect.Struct, true) { + log.Panicf("DI: t[reflect.Type] must be specified but is %v", t) + } + + td, err := cr.buildDefinition(t) + if nil != err { + log.Panicf("DI: %v", err) + } + + if _, ok := cr.definitionByType[td.Type]; ok { + log.Panicf("DI: The type[%s] of Component is exist already", td.FullName) + } + cr.definitionByType[td.Type] = td + + name := td.TypeName + + if a := td.GetTypeAnnotationByType(cdia.ComponentAnnotationType, true); nil != a { + ca := a.(*cdia.ComponentAnnotation) + if "" != strings.Trim(ca.Name, " ") { + name = ca.Name + } + } + + if eTD, ok := cr.definitionByName[name]; ok { + log.Panicf("DI: The name[%s] of Component is exist already type[%s]", name, eTD.FullName) + } + cr.definitionByName[name] = td +} + +func RegisterResource(name string, resource interface{}) error { + return registry.RegisterResource(name, resource) +} +func (cr *defaultComponentRegistry) RegisterResource(name string, resource interface{}) error { + if _, ok := cr.resourceByName[name]; ok { + return fmt.Errorf("DI: Resource[%s] is already exist", name) + } + cr.resourceByName[name] = resource + + return nil +} + +// GetInstance returns instance of type +// t must be pointer of struct +func GetInstance(t reflect.Type) (interface{}, error) { + return registry.GetInstance(t) +} +func (cr *defaultComponentRegistry) GetInstance(t reflect.Type) (instance interface{}, err error) { + if nil == t { + return nil, fmt.Errorf("DI: t[reflect.Type] is nil") + } + + if reflect.Ptr != t.Kind() { + return nil, fmt.Errorf("DI: t[reflect.Type] must be pointer of struct") + } + + if reflect.Struct != t.Elem().Kind() { + return nil, fmt.Errorf("DI: t[reflect.Type] must be pointer of struct") + } + + var ( + td *TypeDefinition + comV interface{} + ok bool + ) + + rt, _, _ := cur.GetTypeInfo(t) + if td, ok = cr.definitionByType[t]; !ok { + if td, err = cr.buildDefinition(t); nil != err { + return nil, fmt.Errorf("DI: %v", err) + } + } + + if comV, ok = cr.instanceByType[td.RealType]; ok { + return comV, nil + } + + v := reflect.New(rt) + rv := v.Elem() + + instance = v.Interface() + cr.instanceByType[td.RealType] = instance + err = nil + defer func() { + if nil != err { + instance = nil + delete(cr.instanceByType, td.RealType) + } + }() + + var ( + annotation cda.Annotation + fV interface{} + ) + + for _, fd := range td.Fields { + f := rv.FieldByName(fd.FieldName) + + if !f.IsValid() { + err = fmt.Errorf("DI: Field[%s] is not valid", fd.FieldName) + return + } + if !f.CanSet() { + err = fmt.Errorf("DI: Field[%s] can not set", fd.FieldName) + return + } + + if annotation = fd.GetAnnotationByType(cdia.InjectAnnotationType, false); nil != annotation { + if fV, err = cr.GetInstance(fd.Type); nil != err { + return + } + } + + if annotation = fd.GetAnnotationByType(cdia.ResourceAnnotationType, false); nil != annotation { + n := annotation.(*cdia.ResourceAnnotation).Name + if "" == n { + n = fd.FieldName + } + if fV, err = cr.GetInstanceByName(n); nil != err { + return + } + } + + if nil != err { + return + } + f.Set(reflect.ValueOf(fV)) + } + + return +} + +func GetInstanceByName(name string) (interface{}, error) { + return registry.GetInstanceByName(name) +} +func (cr *defaultComponentRegistry) GetInstanceByName(name string) (interface{}, error) { + v, ok := cr.resourceByName[name] + if ok { + return v, nil + } + return nil, fmt.Errorf("DI: Resource[%s] is not exist", name) +} + +// GetInstances returns instance of annotated +// n must be name of registered annotation +func GetInstances(ts []reflect.Type) ([]interface{}, error) { + return registry.GetInstances(ts) +} +func (cr *defaultComponentRegistry) GetInstances(ts []reflect.Type) ([]interface{}, error) { + var ( + i interface{} + err error + ) + instances := make([]interface{}, 0) + for _, t := range ts { + if i, err = cr.GetInstance(t); nil != err { + return nil, err + } + instances = append(instances, i) + } + + return instances, nil +} + +func GetInstancesByAnnotationType(t reflect.Type) ([]interface{}, error) { + return registry.GetInstancesByAnnotationType(t) +} +func (cr *defaultComponentRegistry) GetInstancesByAnnotationType(t reflect.Type) ([]interface{}, error) { + var ( + i interface{} + err error + ) + instances := make([]interface{}, 0) + + for _, td := range cr.definitionByType { + if nil != td.GetTypeAnnotationByType(t, true) { + if i, err = cr.GetInstance(td.Type); nil != err { + return nil, err + } + instances = append(instances, i) + } + } + + return instances, nil +} + +func GetTypeAnnotation(instanceType reflect.Type, annotationType reflect.Type) cda.Annotation { + return registry.GetTypeAnnotation(instanceType, annotationType) +} +func (cr *defaultComponentRegistry) GetTypeAnnotation(instanceType reflect.Type, annotationType reflect.Type) cda.Annotation { + def, ok := cr.definitionByType[instanceType] + if !ok { + return nil + } + + return def.GetTypeAnnotationByType(annotationType, false) +} +func GetMethodAnnotation(instanceType reflect.Type, annotationType reflect.Type, methodName string) cda.Annotation { + return registry.GetMethodAnnotation(instanceType, annotationType, methodName) +} +func (cr *defaultComponentRegistry) GetMethodAnnotation(instanceType reflect.Type, annotationType reflect.Type, methodName string) cda.Annotation { + def, ok := cr.definitionByType[instanceType] + if !ok { + return nil + } + + return def.GetMethodAnnotationByType(annotationType, methodName) +} + +func GetMethodAnnotations(instanceType reflect.Type, annotationType reflect.Type) map[string]cda.Annotation { + return registry.GetMethodAnnotations(instanceType, annotationType) +} +func (cr *defaultComponentRegistry) GetMethodAnnotations(instanceType reflect.Type, annotationType reflect.Type) map[string]cda.Annotation { + def, ok := cr.definitionByType[instanceType] + if !ok { + return nil + } + + return def.GetMethodAnnotationsByType(annotationType) +} + +func (cr *defaultComponentRegistry) buildDefinition(t reflect.Type) (*TypeDefinition, error) { + if nil == t { + return nil, fmt.Errorf("t[reflect.Type] is nil") + } + + rt, pkgName, tName := cur.GetTypeInfo(t) + td := &TypeDefinition{} + td.FullName = FullName(pkgName, tName) + td.PkgName = pkgName + td.TypeName = tName + td.Type = t + td.RealType = rt + td.Fields = make([]*FieldDefinition, 0) + + parseFields(rt, td) + + return td, nil +} + +func parseFields(t reflect.Type, td *TypeDefinition) { + // fields := make([]*FieldDefinition, 0) + rt, _, _ := cur.GetTypeInfo(t) + if reflect.Struct != rt.Kind() { + return + } + + for i := 0; i < rt.NumField(); i++ { + f := rt.Field(i) + + if f.Anonymous { + if parseAnonymousField(&f, td) { + continue + } + parseFields(f.Type, td) + } else { + if parseMethodAnnotation(&f, td) { + continue + } + as, err := cda.ParseAnnotation(f.Tag) + if nil != err { + return + } + if nil != as && 0 < len(as) { + fRT, fPkgName, fTName := cur.GetTypeInfo(f.Type) + + fd := &FieldDefinition{ + FieldName: f.Name, + PkgName: fPkgName, + TypeName: fTName, + Type: f.Type, + RealType: fRT, + Annotations: as, + } + td.Fields = append(td.Fields, fd) + } + } + } +} + +func parseAnonymousField(f *reflect.StructField, td *TypeDefinition) bool { + if parseTypeAnnotation(f, td) { + return true + } + return false +} + +func parseTypeAnnotation(f *reflect.StructField, td *TypeDefinition) bool { + // if !haveEmbeddingOf(cda.TypeAnnotationType, f.Type) { + // return + // } + if cda.TypeAnnotationType != f.Type { + return false + } + + as, err := cda.ParseAnnotation(f.Tag) + if nil != err { + return false + } + + if nil != as && 0 < len(as) { + td.TypeAnnotations = as + return true + } + return false +} + +func parseMethodAnnotation(f *reflect.StructField, td *TypeDefinition) bool { + if cda.MethodAnnotationType != f.Type { + return false + } + + as, err := cda.ParseAnnotation(f.Tag) + if nil != err { + return false + } + + if nil != as && 0 < len(as) { + if nil == td.MethodAnnotations { + td.MethodAnnotations = make(map[string]map[reflect.Type]cda.Annotation, 0) + } + td.MethodAnnotations[f.Name[1:]] = as + return true + } + return false +} + +func haveEmbeddingOf(t reflect.Type, target reflect.Type) bool { + if t == target { + return true + } + + rt, _, _ := cur.GetTypeInfo(target) + if reflect.Struct != rt.Kind() { + return false + } + + for i := 0; i < rt.NumField(); i++ { + f := rt.Field(i) + + if f.Anonymous { + if haveEmbeddingOf(t, f.Type) { + return true + } + } + } + return false +} diff --git a/registry/registry_test.go b/registry/registry_test.go new file mode 100644 index 0000000..a6b2f69 --- /dev/null +++ b/registry/registry_test.go @@ -0,0 +1,68 @@ +package registry + +import ( + "log" + "reflect" + "testing" + + "git.loafle.net/overflow/di-go/annotation" + cdia "git.loafle.net/overflow/di-go/injection/annotation" +) + +func TestRegisterType(t *testing.T) { + var ( + err error + css []interface{} + cs interface{} + ) + RegisterType(reflect.TypeOf((*AService)(nil))) + RegisterType(reflect.TypeOf((*BService)(nil))) + // RegisterType(reflect.TypeOf((*TestStruct2)(nil)), &cda.ComponentAnnotation{ + // Name: "test1", + // }) + // RegisterType(reflect.TypeOf((*TestStruct3)(nil)), &cda.ComponentAnnotation{ + // Name: "test2", + // }) + + // fs := getFields(reflect.TypeOf((*TestStruct3)(nil))) + // log.Printf("%v", fs) + + RegisterResource("List", []string{"dfdkf", "skgkfg"}) + + if css, err = GetInstancesByAnnotationType(cdia.ComponentAnnotationType); nil != err { + log.Printf("%v \n", err) + } + log.Printf("%v", css) + + if css, err = GetInstances([]reflect.Type{ + reflect.TypeOf((*AService)(nil)), + reflect.TypeOf((*BService)(nil)), + }); nil != err { + log.Printf("%v \n", err) + } + log.Printf("%v", css) + + if cs, err = GetInstance(reflect.TypeOf((*CService)(nil))); nil != err { + log.Printf("%v \n", err) + } + log.Printf("%v", cs.(*CService)) + +} + +type AService struct { + annotation.TypeAnnotation `annotation:"@Component(name='dkdkdf')"` + + NameA string +} + +type BService struct { + annotation.TypeAnnotation `annotation:"@Component()"` + + NameB string +} + +type CService struct { + AService *AService `annotation:"@Inject()"` + BService *BService `annotation:"@Inject()"` + List []string `annotation:"@Resource()"` +} diff --git a/registry_test.go b/registry_test.go new file mode 100644 index 0000000..7e8391e --- /dev/null +++ b/registry_test.go @@ -0,0 +1,472 @@ +package di + +import ( + "reflect" + "testing" + + oa "git.loafle.net/overflow/annotation-go" + _ "git.loafle.net/overflow/di-go/annotation" +) + +var InjectableServiceType = reflect.TypeOf((*InjectableService)(nil)) + +type InjectableService struct { + oa.TypeAnnotation `annotation:"@Injectable('name': 'InjectableService')"` + Count int + Category string +} + +var InjectServiceType = reflect.TypeOf((*InjectService)(nil)) + +type InjectService struct { + Service *InjectableService `annotation:"@Inject('name': 'InjectableService')"` + + R string +} + +func TestNew(t *testing.T) { + type args struct { + parent Registry + } + tests := []struct { + name string + args args + want Registry + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := New(tt.args.parent); !reflect.DeepEqual(got, tt.want) { + t.Errorf("New() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRegisterType(t *testing.T) { + type args struct { + t reflect.Type + } + tests := []struct { + name string + args args + }{ + // TODO: Add test cases. + { + name: "InjectableService", + args: args{ + t: InjectableServiceType, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + RegisterType(tt.args.t) + }) + } +} + +func TestInstanceRegistry_RegisterType(t *testing.T) { + type fields struct { + parent Registry + definitionByType map[reflect.Type]*TypeDefinition + definitionByName map[string]*TypeDefinition + instanceByType map[reflect.Type]interface{} + instanceByName map[string]interface{} + } + type args struct { + t reflect.Type + } + tests := []struct { + name string + fields fields + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &InstanceRegistry{ + parent: tt.fields.parent, + definitionByType: tt.fields.definitionByType, + definitionByName: tt.fields.definitionByName, + instanceByType: tt.fields.instanceByType, + instanceByName: tt.fields.instanceByName, + } + r.RegisterType(tt.args.t) + }) + } +} + +func TestRegisterResource(t *testing.T) { + type args struct { + name string + resource interface{} + } + tests := []struct { + name string + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := RegisterResource(tt.args.name, tt.args.resource); (err != nil) != tt.wantErr { + t.Errorf("RegisterResource() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestInstanceRegistry_RegisterResource(t *testing.T) { + type fields struct { + parent Registry + definitionByType map[reflect.Type]*TypeDefinition + definitionByName map[string]*TypeDefinition + instanceByType map[reflect.Type]interface{} + instanceByName map[string]interface{} + } + type args struct { + name string + resource interface{} + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &InstanceRegistry{ + parent: tt.fields.parent, + definitionByType: tt.fields.definitionByType, + definitionByName: tt.fields.definitionByName, + instanceByType: tt.fields.instanceByType, + instanceByName: tt.fields.instanceByName, + } + if err := r.RegisterResource(tt.args.name, tt.args.resource); (err != nil) != tt.wantErr { + t.Errorf("InstanceRegistry.RegisterResource() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestInstanceRegistry_buildDefinition(t *testing.T) { + type fields struct { + parent Registry + definitionByType map[reflect.Type]*TypeDefinition + definitionByName map[string]*TypeDefinition + instanceByType map[reflect.Type]interface{} + instanceByName map[string]interface{} + } + type args struct { + t reflect.Type + } + tests := []struct { + name string + fields fields + args args + want *TypeDefinition + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &InstanceRegistry{ + parent: tt.fields.parent, + definitionByType: tt.fields.definitionByType, + definitionByName: tt.fields.definitionByName, + instanceByType: tt.fields.instanceByType, + instanceByName: tt.fields.instanceByName, + } + got, err := r.buildDefinition(tt.args.t) + if (err != nil) != tt.wantErr { + t.Errorf("InstanceRegistry.buildDefinition() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("InstanceRegistry.buildDefinition() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetInstance(t *testing.T) { + RegisterType(InjectableServiceType) + + type args struct { + t reflect.Type + } + tests := []struct { + name string + args args + wantErr bool + }{ + // TODO: Add test cases. + { + name: "InjectService", + args: args{ + t: InjectServiceType, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetInstance(tt.args.t) + if (err != nil) != tt.wantErr { + t.Errorf("GetInstance() error = %v, wantErr %v", err, tt.wantErr) + return + } + t.Log(got) + }) + } +} + +func TestInstanceRegistry_GetInstance(t *testing.T) { + type fields struct { + parent Registry + definitionByType map[reflect.Type]*TypeDefinition + definitionByName map[string]*TypeDefinition + instanceByType map[reflect.Type]interface{} + instanceByName map[string]interface{} + } + type args struct { + t reflect.Type + } + tests := []struct { + name string + fields fields + args args + wantInstance interface{} + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &InstanceRegistry{ + parent: tt.fields.parent, + definitionByType: tt.fields.definitionByType, + definitionByName: tt.fields.definitionByName, + instanceByType: tt.fields.instanceByType, + instanceByName: tt.fields.instanceByName, + } + gotInstance, err := r.GetInstance(tt.args.t) + if (err != nil) != tt.wantErr { + t.Errorf("InstanceRegistry.GetInstance() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotInstance, tt.wantInstance) { + t.Errorf("InstanceRegistry.GetInstance() = %v, want %v", gotInstance, tt.wantInstance) + } + }) + } +} + +func TestGetInstanceByName(t *testing.T) { + type args struct { + name string + } + tests := []struct { + name string + args args + want interface{} + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetInstanceByName(tt.args.name) + if (err != nil) != tt.wantErr { + t.Errorf("GetInstanceByName() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetInstanceByName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestInstanceRegistry_GetInstanceByName(t *testing.T) { + type fields struct { + parent Registry + definitionByType map[reflect.Type]*TypeDefinition + definitionByName map[string]*TypeDefinition + instanceByType map[reflect.Type]interface{} + instanceByName map[string]interface{} + } + type args struct { + name string + } + tests := []struct { + name string + fields fields + args args + want interface{} + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &InstanceRegistry{ + parent: tt.fields.parent, + definitionByType: tt.fields.definitionByType, + definitionByName: tt.fields.definitionByName, + instanceByType: tt.fields.instanceByType, + instanceByName: tt.fields.instanceByName, + } + got, err := r.GetInstanceByName(tt.args.name) + if (err != nil) != tt.wantErr { + t.Errorf("InstanceRegistry.GetInstanceByName() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("InstanceRegistry.GetInstanceByName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetInstances(t *testing.T) { + type args struct { + ts []reflect.Type + } + tests := []struct { + name string + args args + want []interface{} + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetInstances(tt.args.ts) + if (err != nil) != tt.wantErr { + t.Errorf("GetInstances() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetInstances() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestInstanceRegistry_GetInstances(t *testing.T) { + type fields struct { + parent Registry + definitionByType map[reflect.Type]*TypeDefinition + definitionByName map[string]*TypeDefinition + instanceByType map[reflect.Type]interface{} + instanceByName map[string]interface{} + } + type args struct { + ts []reflect.Type + } + tests := []struct { + name string + fields fields + args args + want []interface{} + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &InstanceRegistry{ + parent: tt.fields.parent, + definitionByType: tt.fields.definitionByType, + definitionByName: tt.fields.definitionByName, + instanceByType: tt.fields.instanceByType, + instanceByName: tt.fields.instanceByName, + } + got, err := r.GetInstances(tt.args.ts) + if (err != nil) != tt.wantErr { + t.Errorf("InstanceRegistry.GetInstances() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("InstanceRegistry.GetInstances() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetInstancesByAnnotationType(t *testing.T) { + type args struct { + at reflect.Type + } + tests := []struct { + name string + args args + want []interface{} + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetInstancesByAnnotationType(tt.args.at) + if (err != nil) != tt.wantErr { + t.Errorf("GetInstancesByAnnotationType() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetInstancesByAnnotationType() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestInstanceRegistry_GetInstancesByAnnotationType(t *testing.T) { + type fields struct { + parent Registry + definitionByType map[reflect.Type]*TypeDefinition + definitionByName map[string]*TypeDefinition + instanceByType map[reflect.Type]interface{} + instanceByName map[string]interface{} + } + type args struct { + at reflect.Type + } + tests := []struct { + name string + fields fields + args args + want []interface{} + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &InstanceRegistry{ + parent: tt.fields.parent, + definitionByType: tt.fields.definitionByType, + definitionByName: tt.fields.definitionByName, + instanceByType: tt.fields.instanceByType, + instanceByName: tt.fields.instanceByName, + } + got, err := r.GetInstancesByAnnotationType(tt.args.at) + if (err != nil) != tt.wantErr { + t.Errorf("InstanceRegistry.GetInstancesByAnnotationType() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("InstanceRegistry.GetInstancesByAnnotationType() = %v, want %v", got, tt.want) + } + }) + } +}