package tanja import ( "reflect" "strings" ) // Registers a pattern for each of the methods of 'obj'. The method will be // called from ses.Dispatch() (and thus ses.Run() if you use that) whenever a // tuple has been received for the pattern. // // The function f() is called for each method with as its argument the name of // that method. The function is expected to return the pattern to be // registered. If nil is returned instead, no pattern will be registered for // the method. // // The method type must conform to the following rules: // - It should be exported (name must start with an uppercase letter) // - It should not have any return values // - It may have any number of arguments, as long as their types are allowed. // - The first argument may be of type *tanja.Message // - If the method is variadic, its last argument must be of type ...Element // - Any other arguments must be of a type accepted by El() // // Methods that do not conform to these rules can not be registered and f() // should return nil for them (Bad Things happen if it doesn't). // // If the first argument is of type *tanja.Message, the corresponding message // is passed to the method. The method is then also registered with willReply // set to true, so it MUST eventually call Message.Close(). // If the first argument is not of type *tanja.Message, the method will have no // way of replying to the incoming tuple and is thus registered with willReply // set to false. // // Any remaining arguments are used to capture (and in a limited sense, // validate) elements from the remainder of the received tuple. The 'remainder' // being any elements after the pattern returned by f(). If no matching element // exist in the received tuple or if the element can not be converted into the // type that the method accepts, the method will not be called. // // If the method is variadic, any remaining elements in the tuple (after those // captured by any previous arguments) will be passed through to the last // argument. // // Returns a list of all *Registrations made, or nil of nothing has been registered. // // TODO: Examples are required to really understand the use of this method. func (s *Session) RegRPC(obj interface{}, f func(string) Tuple) []*Registration { var lst []*Registration objv := reflect.ValueOf(obj) objt := objv.Type() num := 0 for i := 0; i < objt.NumMethod(); i++ { mm := objt.Method(i) // get pattern pat := f(mm.Name) if pat == nil { continue } // Determine how many elements to capture, and pad that many nil's to // the pattern. (Ensures we only match tuples with enough elements) mv := objv.Method(i) mt := mv.Type() offset := len(pat) willReply := false j := 0 if mt.NumIn() > 0 && mt.In(0) == reflect.TypeOf((*Message)(nil)) { willReply = true j++ } end := mt.NumIn() if mt.IsVariadic() { end-- } for ; j < end; j++ { pat = append(pat, El(nil)) } // Generate callback and register r := s.Registert(willReply, pat) r.Callback(regRPCGenCallback(mt, mv, offset, willReply)) lst = append(lst, r) num++ } return lst } // Should be mostly the reverse of El(). Returns the zero value if the type is // not supported or the element could not be converted. func regRPCValue(at reflect.Type, el Element) (val reflect.Value) { // Matching should be on 'at' itself rather than it's kind, since the type // should really be a basic one. But I suspect that this is faster, as // reflect.TypeOf() calls are probably not done at compile-time. switch at.Kind() { case reflect.Struct: if at == reflect.TypeOf(Element{nil}) { val = reflect.ValueOf(el) } case reflect.String: if v, ok := el.IsString(); ok { val = reflect.New(at).Elem() val.SetString(v) } case reflect.Bool: val = reflect.ValueOf(el.Bool()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if v, ok := el.IsInt(); ok { val = reflect.New(at).Elem() val.SetInt(v) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if v, ok := el.IsInt(); ok && v >= 0 { val = reflect.New(at).Elem() val.SetUint(uint64(v)) } case reflect.Float32, reflect.Float64: if v, ok := el.IsFloat(); ok { val = reflect.New(at).Elem() val.SetFloat(v) } case reflect.Slice: if v, ok := el.E.([]Element); ok && at == reflect.TypeOf(el.E) { val = reflect.ValueOf(v) } case reflect.Map: if v, ok := el.E.(map[string]Element); ok && at == reflect.TypeOf(el.E) { val = reflect.ValueOf(v) } } return } func regRPCGenCallback(mt reflect.Type, mv reflect.Value, offset int, willReply bool) func(*Message) { return func(msg *Message) { args := make([]reflect.Value, mt.NumIn()) arg := 0 if willReply { args[0] = reflect.ValueOf(msg) arg++ } off := offset end := mt.NumIn() if mt.IsVariadic() { end-- } for arg < end { args[arg] = regRPCValue(mt.In(arg), msg.Tup[off]) if !args[arg].IsValid() { msg.Close() return } off++ arg++ } if mt.IsVariadic() { args[arg] = reflect.ValueOf(msg.Tup[off:]) mv.CallSlice(args) } else { mv.Call(args) } } } // Handy wrapper for RegRPC(), where each method-to-be-registered has a common // prefix in the name, and where the pattern consists of another prefix + the // rest of the method name. (The source code is easier to understand than this // explanation, really) func (s *Session) RegPrefix(obj interface{}, pat Tuple, prefix string, tolower bool) []*Registration { return s.RegRPC(obj, func(n string) Tuple { if !strings.HasPrefix(n, prefix) { return nil } else if tolower { return append(pat, Element{strings.ToLower(n[len(prefix):])}) } return append(pat, Element{n[len(prefix):]}) }) }