blob: f6516288d5bf9d961908139c0d038b207a6195af [file] [log] [blame]
/*
* Copyright 2015 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package rtdb
import (
"fmt"
"reflect"
)
var (
errorType = reflect.TypeOf((*error)(nil)).Elem()
objectBaseType = reflect.TypeOf((*ObjectBase)(nil)).Elem()
)
// ObjectBase
type ObjectBase interface {
PostLoad ()
}
// Method
type Method struct {
Method reflect.Method // method
ArgTypes []reflect.Type // method argument types
RetType reflect.Type // return type (in addition to error)
}
// Module
type Module struct {
Name string // name of object
PtrType reflect.Type // type of pointer to object
Methods map[string]*Method // callable methods in object
}
// ObjectModel
type ObjectModel struct {
moduleByName map[string]*Module
moduleByType map[reflect.Type]*Module
}
func getObjectMethods (ptrType reflect.Type) map[string]*Method {
methods := make(map[string]*Method, 0)
for methodNdx := 0; methodNdx < ptrType.NumMethod(); methodNdx++ {
method := ptrType.Method(methodNdx)
mType := method.Type
// Method must be visible.
if method.PkgPath != "" { continue }
// Must have at least one argument (destination object).
numArgs := mType.NumIn()
if numArgs == 0 { continue }
// Must have one or two output values (optional value and error).
numOut := mType.NumOut()
if numOut == 0 || numOut > 2 { continue }
// Get argument types.
argTypes := make([]reflect.Type, numArgs - 1)
for argNdx := 0; argNdx < numArgs - 1; argNdx++ {
argTypes[argNdx] = mType.In(argNdx + 1)
}
// With two output arguments, first must be return value.
var retType reflect.Type
if numOut == 2 {
retType = mType.Out(0)
}
// Last output value must be error.
errType := mType.Out(numOut - 1)
if errType != errorType { continue }
methods[method.Name] = &Method {
Method: method,
ArgTypes: argTypes,
RetType: retType,
}
}
return methods
}
func newModule (ptrType reflect.Type) *Module {
model := Module {
Name: ptrType.Elem().Name(),
PtrType: ptrType,
Methods: getObjectMethods(ptrType),
}
return &model
}
func NewObjectModel (objectTypes []reflect.Type) *ObjectModel {
// Create moduleByType and moduleByName maps.
moduleByName := make(map[string]*Module, 0)
moduleByType := make(map[reflect.Type]*Module, 0)
for _, objType := range objectTypes {
ptrType := reflect.PtrTo(objType)
if !ptrType.Implements(objectBaseType) {
panic(fmt.Errorf("[object] type %s doesn't implement ObjectBase interface", objType.Name()))
}
module := newModule(ptrType)
moduleByName[objType.Name()] = module
moduleByType[ptrType] = module
}
return &ObjectModel {
moduleByName: moduleByName,
moduleByType: moduleByType,
}
}
func (model *ObjectModel) NewObject (typeName string) (ObjectBase, error) {
// Find module by name.
module, ok := model.moduleByName[typeName]
if !ok {
return nil, fmt.Errorf("[object] no such module: %s", typeName)
}
// Instantiate object and return it.
object := reflect.New(module.PtrType.Elem())
return object.Interface().(ObjectBase), nil
}
func (model *ObjectModel) Cast (dst ObjectBase, methodName string, args ...interface{}) error {
// Check that destination object is a known type.
dstType := reflect.TypeOf(dst)
module, ok := model.moduleByType[dstType]
if !ok {
return fmt.Errorf("[object] invalid module type: %s", dstType.Name())
}
// Check that method is a known one.
method, ok := module.Methods[methodName]
if !ok {
return fmt.Errorf("[object] no method '%s' in module '%s'", methodName, module.Name)
}
// Fill method argument values.
numArgs := len(args)
values := make([]reflect.Value, numArgs + 1)
values[0] = reflect.ValueOf(dst)
for i, _ := range args {
values[i+1] = reflect.ValueOf(args[i])
}
// Call method.
retValues := method.Method.Func.Call(values) // \todo [petri] handle return value!
errValue := retValues[len(retValues) - 1]
err := errValue.Interface()
if err != nil {
return err.(error)
} else {
return nil
}
}