123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- package neffos
- import (
- "reflect"
- "strings"
- )
- func indirectType(typ reflect.Type) reflect.Type {
- if typ.Kind() == reflect.Ptr {
- typ = typ.Elem()
- }
- return typ
- }
- func isZero(v reflect.Value) bool {
- switch v.Kind() {
- case reflect.Struct:
- zero := true
- for i := 0; i < v.NumField(); i++ {
- zero = zero && isZero(v.Field(i))
- }
- if typ := v.Type(); typ != nil && v.IsValid() {
- f, ok := typ.MethodByName("IsZero")
- // if not found
- // if has input arguments (1 is for the value receiver, so > 1 for the actual input args)
- // if output argument is not boolean
- // then skip this IsZero user-defined function.
- if !ok || f.Type.NumIn() > 1 || f.Type.NumOut() != 1 && f.Type.Out(0).Kind() != reflect.Bool {
- return zero
- }
- method := v.Method(f.Index)
- // no needed check but:
- if method.IsValid() && !method.IsNil() {
- // it shouldn't panic here.
- zero = method.Call(nil)[0].Interface().(bool)
- }
- }
- return zero
- case reflect.Func, reflect.Map, reflect.Slice:
- return v.IsNil()
- case reflect.Array:
- zero := true
- for i := 0; i < v.Len(); i++ {
- zero = zero && isZero(v.Index(i))
- }
- return zero
- }
- // if not any special type then use the reflect's .Zero
- // usually for fields, but remember if it's boolean and it's false
- // then it's zero, even if set-ed.
- if !v.CanInterface() {
- // if can't interface, i.e return value from unexported field or method then return false
- return false
- }
- zero := reflect.Zero(v.Type())
- return v.Interface() == zero.Interface()
- }
- // does not support child elements on purpose.
- func visitFields(typ reflect.Type, visitor func(f reflect.StructField) bool) int {
- typ = indirectType(typ)
- for n, i := typ.NumField(), 0; i < n; i++ {
- f := typ.Field(i)
- found := visitor(f)
- if found {
- return i
- }
- }
- return -1
- }
- func getNonZeroFields(v reflect.Value) (fields map[int]reflect.Value) {
- v = reflect.Indirect(v)
- visitFields(v.Type(), func(f reflect.StructField) bool {
- fieldIndex := f.Index[0]
- fieldValue := v.Field(fieldIndex)
- if !isZero(fieldValue) {
- if fields == nil {
- fields = make(map[int]reflect.Value)
- }
- fields[fieldIndex] = fieldValue
- }
- return false
- })
- return
- }
- func getFieldIndex(forType reflect.Type, fieldType reflect.Type) int {
- return visitFields(forType, func(f reflect.StructField) bool {
- return f.Type == fieldType
- })
- }
- func resolveStructNamespace(v reflect.Value) (string, bool) {
- // By Namespace() string method.
- typ := v.Type()
- method, ok := typ.MethodByName("Namespace")
- if ok {
- if getNamespace, ok := v.Method(method.Index).Interface().(func() string); ok {
- namespace := getNamespace()
- Debugf("Set namespace [\"%s\"] from method [%s.%s]", func() dargs {
- return dargs{namespace, nameOf(typ), method.Name}
- })
- return namespace, true
- }
- }
- // By field Namespace string with filled value.
- typ = indirectType(typ)
- v = reflect.Indirect(v)
- if f, ok := typ.FieldByNameFunc(func(s string) bool { return s == "Namespace" }); ok {
- if f.Type.Kind() == reflect.String {
- namespace := v.Field(f.Index[0]).String()
- Debugf("Set namespace [\"%s\"] from field [%s.%s]", func() dargs {
- return dargs{namespace, nameOf(typ), f.Name}
- })
- return namespace, true
- }
- }
- return "", false
- }
- var (
- nsConnType = reflect.TypeOf((*NSConn)(nil))
- msgType = reflect.TypeOf(Message{})
- errType = reflect.TypeOf((*error)(nil)).Elem()
- )
- func makeMessageHandlerFuncType(forType reflect.Type, nsConnFieldIndex int) reflect.Type {
- // Create the dynamic type which methods will be compared to.
- // remember, the receiver Ptr is also part of the input arguments,
- // that's why we don't use a static type assertion.
- expectedIn := []reflect.Type{
- forType,
- nsConnType,
- msgType,
- }
- if nsConnFieldIndex >= 0 {
- // Except when the Ptr is a dynamic one (has a field of NSConn) then the event callback does not require
- // that on its input arguments.
- expectedIn = append(expectedIn[0:1], expectedIn[2:]...)
- }
- return reflect.FuncOf(expectedIn, []reflect.Type{errType}, false)
- }
- func isArgOf(fnType reflect.Type, argType reflect.Type) bool {
- if fnType.Kind() != reflect.Func {
- panic("isArgOf used on a non-method type")
- }
- for i, n := 0, fnType.NumIn(); i < n; i++ {
- if fnType.In(i) == argType {
- return true
- }
- }
- return false
- }
- func makeEventFromMethod(v reflect.Value, method reflect.Method, eventMatcher EventMatcherFunc) (eventName string, cb MessageHandlerFunc) {
- eventName = method.Name
- // if method looks like a system event, i.e
- // OnNamespaceConnected, then convert its registered event name
- // _OnNamespaceConnected which is the correct.
- // We could accept a func like:
- // func(s *myConn) _OnNamespaceConnected(msg neffos.Message) error
- // but Go linting does not allow this and
- // we don't want our users to have yellow boxes everywhere in their editors.
- if IsSystemEvent("_" + eventName) {
- eventName = "_" + eventName
- }
- if !IsSystemEvent(eventName) {
- if eventMatcher != nil {
- newName, ok := eventMatcher(method.Name)
- if !ok {
- return "", nil
- }
- eventName = newName
- }
- }
- if isArgOf(method.Type, nsConnType) {
- // it should accept NSConn - static "controller".
- cb = v.Method(method.Index).Interface().(func(*NSConn, Message) error)
- } else {
- // the NSConn exists on the "controller" itself which is set dynamically.
- cb = func(c *NSConn, msg Message) error {
- // load an existing instance which contains the same "c".
- return c.value.Method(method.Index).Interface().(func(Message) error)(msg)
- }
- }
- return
- }
- // StructInjector is a type which injects a dynamic struct value.
- // See `Struct.SetInjector` for more.
- type StructInjector func(structType reflect.Type, nsConn *NSConn) (structValue reflect.Value)
- func nameOf(structType reflect.Type) string {
- structType = indirectType(structType)
- typName := structType.Name()
- pkgPath := structType.PkgPath()
- fullname := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + typName
- return fullname
- }
- func makeEventsFromStruct(v reflect.Value, eventMatcher EventMatcherFunc, injector StructInjector) Events {
- events := make(Events)
- typ := v.Type()
- // get the index of field of a "NSConn" type.
- nsConnFieldIndex := getFieldIndex(typ, nsConnType)
- msgHandlerType := makeMessageHandlerFuncType(typ, nsConnFieldIndex)
- for i, n := 0, typ.NumMethod(); i < n; i++ {
- method := typ.Method(i)
- if method.Type != msgHandlerType {
- continue
- }
- eventName, cb := makeEventFromMethod(v, method, eventMatcher)
- if cb == nil {
- continue
- }
- Debugf("Event [\"%s\"] is handled by [%s.%s] method", func() dargs {
- return dargs{eventName, nameOf(typ), method.Name}
- })
- events[eventName] = cb
- }
- if nsConnFieldIndex != -1 {
- typ = indirectType(typ)
- var staticFields map[int]reflect.Value
- if injector == nil {
- // maybe this should be added no matter what, I have to check
- // some things in our company's production server first.
- staticFields = getNonZeroFields(v)
- DebugEach(staticFields, func(idx int, f reflect.Value) {
- fval := f.Interface()
- fname := typ.Field(idx).Name
- if fname == "Namespace" {
- // let's no log this as user field because
- // it's optionally used to provide a namespace on NewStruct.GetNamespaces().
- return
- }
- Debugf("Field [%s.%s] marked as static on value [%v]", nameOf(typ), fname, fval)
- })
- injector = func(typ reflect.Type, nsConn *NSConn) reflect.Value {
- return reflect.New(typ)
- }
- }
- cb, hasNamespaceConnect := events[OnNamespaceConnect]
- events[OnNamespaceConnect] = func(c *NSConn, msg Message) error {
- cachePtr := injector(typ, c)
- cacheElem := cachePtr.Elem()
- // set the NSConn dynamic field.
- cacheElem.Field(nsConnFieldIndex).Set(reflect.ValueOf(c))
- // set any static fields if default injector (see above).
- for findex, fvalue := range staticFields {
- cacheElem.Field(findex).Set(fvalue)
- }
- // Store it for the rest of the events inside
- // this namespace of that specific connection.
- c.value = cachePtr
- if hasNamespaceConnect {
- return cb(c, msg)
- }
- return nil
- }
- }
- return events
- }
|