commit 9b562b23eabf13d5a0ed65f1baa4720c76e3a25a Author: crusader Date: Wed Aug 22 18:36:13 2018 +0900 project initialized 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/annotation.go b/annotation.go new file mode 100644 index 0000000..c634f47 --- /dev/null +++ b/annotation.go @@ -0,0 +1,17 @@ +package annotation + +import ( + "regexp" +) + +const ( + AnnotationTag = "annotation" + + NameTag = "@name" + DefaultTag = "@default" +) + +var ( + AnnotationRGX = regexp.MustCompile(`@((?s).*?)\(((?s).*?)\)`) + AnnotationBodyRGX = regexp.MustCompile(`'([^\\\\']|\\')*'`) +) diff --git a/definition.go b/definition.go new file mode 100644 index 0000000..90d9eff --- /dev/null +++ b/definition.go @@ -0,0 +1,8 @@ +package annotation + +import "reflect" + +type Definition struct { + t reflect.Type + rt reflect.Type +} diff --git a/registry.go b/registry.go new file mode 100644 index 0000000..af00d28 --- /dev/null +++ b/registry.go @@ -0,0 +1,158 @@ +package annotation + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" + + cur "git.loafle.net/overflow/util-go/reflect" +) + +type Registry interface { + Register(t reflect.Type) error + Get(f *reflect.StructField) (map[reflect.Type]Annotation, error) +} + +var SystemRegistry = &AnnotationRegistry{ + parent: nil, + definitions: make(map[string]*Definition, 0), +} + +func New(parent Registry) Registry { + r := &AnnotationRegistry{ + parent: parent, + definitions: make(map[string]*Definition, 0), + } + if nil == r.parent { + r.parent = SystemRegistry + } + return r +} + +type AnnotationRegistry struct { + parent Registry + definitions map[string]*Definition +} + +func Register(t reflect.Type) error { + return SystemRegistry.Register(t) +} +func (r *AnnotationRegistry) Register(t reflect.Type) error { + rt, _, _ := cur.GetTypeInfo(t) + + fields := findAnnotatedFields(t, AnnotationType, false) + switch len(fields) { + case 0: + return fmt.Errorf("type[%s] is not Annotation", rt.Name()) + case 1: + default: + return fmt.Errorf("type[%s] have only one Annotation", rt.Name()) + } + + f := fields[AnnotationName] + name := strings.TrimSpace(f.Tag.Get(NameTag)) + if "" == name { + return fmt.Errorf("annotation name of type[%s] is not valid", rt.Name()) + } + + if _, ok := r.definitions[name]; ok { + return fmt.Errorf("name[%s] of annotation exist already", name) + } + + r.definitions[name] = &Definition{ + t: t, + rt: rt, + } + + return nil +} + +func Get(f *reflect.StructField) (map[reflect.Type]Annotation, error) { + return SystemRegistry.Get(f) +} +func (r *AnnotationRegistry) Get(f *reflect.StructField) (map[reflect.Type]Annotation, error) { + annotations := make(map[reflect.Type]Annotation, 0) + + tag := strings.TrimSpace(f.Tag.Get(AnnotationTag)) + if "" == tag { + return annotations, nil + } + + if !AnnotationRGX.MatchString(tag) { + return nil, fmt.Errorf("Tag of annotation[%s] is not match", tag) + } + + rss := AnnotationRGX.FindAllStringSubmatch(tag, -1) + if nil == rss || 0 == len(rss) { + return annotations, nil + } + + for _, rs := range rss { + if 3 != len(rs) { + return nil, fmt.Errorf("Tag of annotation[%s] is not valid", rs[0]) + } + + name := fmt.Sprintf("@%s", strings.TrimSpace(rs[1])) + body := rs[2] + + if !AnnotationBodyRGX.MatchString(body) { + return nil, fmt.Errorf("Body[%s] of annotation[%s] is not valid", body, name) + } + + body = AnnotationBodyRGX.ReplaceAllStringFunc(body, func(token string) string { + switch len(token) { + case 0, 1, 2: + return "\"\"" + default: + return strings.Replace(fmt.Sprintf("\"%s\"", token[1:len(token)-1]), "\\'", "'", -1) + } + }) + body = fmt.Sprintf("{%s}", strings.TrimSpace(body)) + + def, ok := r.definitions[name] + if !ok { + return nil, fmt.Errorf("annotation[%s] is not exist", name) + } + + v := reflect.New(def.rt) + i := v.Interface() + + err := json.Unmarshal([]byte(body), i) + if nil != err { + return nil, fmt.Errorf("Unmarshal failed %v", err) + } + + annotations[def.t] = i + } + + return annotations, nil +} + +func findAnnotatedFields(t reflect.Type, ft reflect.Type, deep bool) map[string]*reflect.StructField { + fields := make(map[string]*reflect.StructField, 0) + + rt, _, _ := cur.GetTypeInfo(t) + if reflect.Struct != rt.Kind() { + return fields + } + +LOOP: + for i := 0; i < rt.NumField(); i++ { + f := rt.Field(i) + + if f.Anonymous { + if f.Type == ft { + fields[f.Name] = &f + continue LOOP + } + if deep { + _fields := findAnnotatedFields(f.Type, ft, deep) + for _n, _f := range _fields { + fields[_n] = _f + } + } + } + } + return fields +} diff --git a/registry_test.go b/registry_test.go new file mode 100644 index 0000000..7394885 --- /dev/null +++ b/registry_test.go @@ -0,0 +1,208 @@ +package annotation + +import ( + "reflect" + "testing" +) + +var InjectableAnnotationType = reflect.TypeOf((*InjectableAnnotation)(nil)) + +type InjectableAnnotation struct { + Annotation `@name:"@Injectable"` + Name string `json:"name" @default:""` +} + +var TestServiceType = reflect.TypeOf((*TestService)(nil)) + +type TestService struct { + TypeAnnotation `annotation:"@Injectable('name': 'TestService')"` +} + +func TestNew(t *testing.T) { + type args struct { + parent Registry + } + tests := []struct { + name string + args args + want Registry + }{ + { + name: "New of nil", + args: args{ + parent: nil, + }, + want: New(nil), + }, + } + 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 TestRegister(t *testing.T) { + type args struct { + t reflect.Type + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "Register Injectable", + args: args{ + t: InjectableAnnotationType, + }, + wantErr: false, + }, + { + name: "Register duplicate Injectable", + args: args{ + t: InjectableAnnotationType, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := Register(tt.args.t); (err != nil) != tt.wantErr { + t.Errorf("Register() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestAnnotationRegistry_Register(t *testing.T) { + type fields struct { + parent Registry + definitions map[string]*Definition + } + type args struct { + t reflect.Type + } + 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 := &AnnotationRegistry{ + parent: tt.fields.parent, + definitions: tt.fields.definitions, + } + if err := r.Register(tt.args.t); (err != nil) != tt.wantErr { + t.Errorf("AnnotationRegistry.Register() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestGet(t *testing.T) { + Register(InjectableAnnotationType) + + ts := TestService{} + tsT := reflect.TypeOf(ts) + + f, _ := tsT.FieldByName("TypeAnnotation") + + type args struct { + f *reflect.StructField + } + tests := []struct { + name string + args args + want map[reflect.Type]Annotation + wantErr bool + }{ + { + name: "Get TestService", + args: args{ + f: &f, + }, + want: map[reflect.Type]Annotation{ + TestServiceType: &InjectableAnnotation{ + Name: "TestService", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Get(tt.args.f) + if (err != nil) != tt.wantErr { + t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Get() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestAnnotationRegistry_Get(t *testing.T) { + type fields struct { + parent Registry + definitions map[string]*Definition + } + type args struct { + f *reflect.StructField + } + tests := []struct { + name string + fields fields + args args + want map[reflect.Type]Annotation + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &AnnotationRegistry{ + parent: tt.fields.parent, + definitions: tt.fields.definitions, + } + got, err := r.Get(tt.args.f) + if (err != nil) != tt.wantErr { + t.Errorf("AnnotationRegistry.Get() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("AnnotationRegistry.Get() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_findAnnotatedFields(t *testing.T) { + type args struct { + t reflect.Type + ft reflect.Type + deep bool + } + tests := []struct { + name string + args args + want map[string]*reflect.StructField + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := findAnnotatedFields(tt.args.t, tt.args.ft, tt.args.deep); !reflect.DeepEqual(got, tt.want) { + t.Errorf("findAnnotatedFields() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/type.go b/type.go new file mode 100644 index 0000000..6ec55c9 --- /dev/null +++ b/type.go @@ -0,0 +1,28 @@ +package annotation + +import "reflect" + +var AnnotationType = reflect.TypeOf((*Annotation)(nil)).Elem() + +const AnnotationName = "Annotation" + +type Annotation interface { +} + +var TypeAnnotationType = reflect.TypeOf((*TypeAnnotation)(nil)).Elem() + +type TypeAnnotation interface { + Annotation +} + +var ConstructorAnnotationType = reflect.TypeOf((*ConstructorAnnotation)(nil)).Elem() + +type ConstructorAnnotation interface { + Annotation +} + +var MethodAnnotationType = reflect.TypeOf((*MethodAnnotation)(nil)).Elem() + +type MethodAnnotation interface { + Annotation +}