pojo.go (336 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 hessian import ( "fmt" "reflect" "strings" "sync" "unicode" ) import ( perrors "github.com/pkg/errors" ) // invalid consts const ( InvalidJavaEnum JavaEnum = -1 ClassKey = "_class" ) // struct filed tag of hessian var tagIdentifier = "hessian" // SetTagIdentifier for customize struct filed tag of hessian, your can use it like: // // hessian.SetTagIdentifier("json") // type MyUser struct { // UserFullName string `json:"user_full_name"` // FamilyPhoneNumber string // default convert to => familyPhoneNumber // } // var user MyUser // hessian.NewEncoder().Encode(user) func SetTagIdentifier(s string) { tagIdentifier = s } // POJO interface // !!! Pls attention that Every field name should be upper case. // Otherwise the app may panic. type POJO interface { JavaClassName() string // got a go struct's Java Class package name which should be a POJO class. } // POJOEnum enum for POJO type POJOEnum interface { POJO String() string EnumValue(string) JavaEnum } // JavaEnum type type JavaEnum int32 // JavaEnumClass struct type JavaEnumClass struct { name string } type ClassInfo struct { javaName string fieldNameList []string buffer []byte // encoded buffer } type structInfo struct { typ reflect.Type goName string javaName string index int // classInfoList index inst interface{} } // POJORegistry pojo registry struct type POJORegistry struct { sync.RWMutex classInfoList []*ClassInfo // {class name, field name list...} list j2g map[string]string // java class name --> go struct name registry map[string]*structInfo // go class name --> go struct info } var ( pojoRegistry = &POJORegistry{ j2g: make(map[string]string), registry: make(map[string]*structInfo), } pojoType = reflect.TypeOf((*POJO)(nil)).Elem() javaEnumType = reflect.TypeOf((*POJOEnum)(nil)).Elem() ) // initDefBuffer initial the class definition buffer, which can be used repeatedly. func (c *ClassInfo) initDefBuffer() { if len(c.buffer) == 0 { c.buffer = encByte(c.buffer, BC_OBJECT_DEF) c.buffer = encString(c.buffer, c.javaName) c.buffer = encInt32(c.buffer, int32(len(c.fieldNameList))) for _, fieldName := range c.fieldNameList { c.buffer = encString(c.buffer, fieldName) } } } // struct parsing func showPOJORegistry() { pojoRegistry.Lock() for k, v := range pojoRegistry.registry { fmt.Println("-->> show Registered types <<----") fmt.Println(k, v) } pojoRegistry.Unlock() } // RegisterPOJO Register a POJO instance. The return value is -1 if @o has been registered. func RegisterPOJO(o POJO) int { return RegisterPOJOMapping(o.JavaClassName(), o) } // RegisterPOJOMapping Register a POJO instance. The return value is -1 if @o has been registered. func RegisterPOJOMapping(javaClassName string, o interface{}) int { // # definition for an object (compact map) // class-def ::= 'C' string int string* pojoRegistry.Lock() defer pojoRegistry.Unlock() if goName, ok := pojoRegistry.j2g[javaClassName]; ok { // TODO print warning message about duplicate registration JavaClass return pojoRegistry.registry[goName].index } // JavaClassName shouldn't equal to goName if _, ok := pojoRegistry.registry[javaClassName]; ok { return -1 } return registerPOJOTypeMapping(javaClassName, GetGoType(o), obtainValueType(o), o) } // registerPOJOTypeMapping Register a POJO instance for given type. // It's used internally to register special types directly. func registerPOJOTypeMapping(javaClassName string, goName string, typ reflect.Type, o interface{}) int { var ( bHeader []byte bBody []byte fieldList []string sttInfo structInfo clsDef ClassInfo ) sttInfo.typ = typ sttInfo.goName = goName sttInfo.javaName = javaClassName sttInfo.inst = o pojoRegistry.j2g[sttInfo.javaName] = sttInfo.goName registerListNameMapping(sttInfo.goName, sttInfo.javaName) // prepare fields info of objectDef nextStruct := []reflect.Type{sttInfo.typ} for len(nextStruct) > 0 { current := nextStruct[0] if current.Kind() == reflect.Struct { for i := 0; i < current.NumField(); i++ { // skip unexported anonymous filed if current.Field(i).PkgPath != "" { continue } structField := current.Field(i) // skip ignored field tagVal, hasTag := structField.Tag.Lookup(tagIdentifier) if tagVal == `-` { continue } // flat anonymous field if structField.Anonymous && structField.Type.Kind() == reflect.Struct { nextStruct = append(nextStruct, structField.Type) continue } var fieldName string if hasTag { fieldName = tagVal } else { fieldName = lowerCamelCase(structField.Name) } fieldList = append(fieldList, fieldName) bBody = encString(bBody, fieldName) } } nextStruct = nextStruct[1:] } // prepare header of objectDef bHeader = encByte(bHeader, BC_OBJECT_DEF) bHeader = encString(bHeader, sttInfo.javaName) // write fields length into header of objectDef // note: cause fieldList is a dynamic slice, so one must calculate length only after it being prepared already. bHeader = encInt32(bHeader, int32(len(fieldList))) // prepare classDef clsDef = ClassInfo{javaName: sttInfo.javaName, fieldNameList: fieldList} // merge header and body of objectDef into buffer of ClassInfo clsDef.buffer = append(bHeader, bBody...) sttInfo.index = len(pojoRegistry.classInfoList) pojoRegistry.classInfoList = append(pojoRegistry.classInfoList, &clsDef) pojoRegistry.registry[sttInfo.goName] = &sttInfo return sttInfo.index } // UnRegisterPOJOs unregister POJO instances. It is easy for test. func UnRegisterPOJOs(os ...POJO) []int { arr := make([]int, len(os)) for i := range os { arr[i] = unRegisterPOJO(os[i]) } return arr } func unRegisterPOJO(o POJO) int { pojoRegistry.Lock() defer pojoRegistry.Unlock() goName := GetGoType(o) if pojoStructInfo, ok := pojoRegistry.registry[goName]; ok { delete(pojoRegistry.j2g, pojoStructInfo.javaName) listTypeNameMapper.Delete(pojoStructInfo.goName) // remove registry cache. delete(pojoRegistry.registry, pojoStructInfo.goName) // don't remove registry classInfoList, // indexes of registered pojo may be affected. return pojoStructInfo.index } return -1 } // GetGoType get the raw go type name with package. func GetGoType(o interface{}) string { return combineGoTypeName(reflect.TypeOf(o)) } func combineGoTypeName(t reflect.Type) string { for reflect.Ptr == t.Kind() { t = t.Elem() } if reflect.Slice == t.Kind() { goName := t.String() sliceArrayPrefixIndex := strings.LastIndex(goName, "]") for reflect.Slice == t.Kind() { t = t.Elem() } return goName[:sliceArrayPrefixIndex+1] + combineGoTypeName(t) } pkgPath := t.PkgPath() goName := t.String() if pkgPath == "" || strings.HasPrefix(goName, pkgPath) { return goName } return pkgPath + "/" + goName } func obtainValueType(o interface{}) reflect.Type { v := reflect.ValueOf(o) switch v.Kind() { case reflect.Struct: return v.Type() case reflect.Ptr: return v.Elem().Type() } return reflect.TypeOf(o) } // RegisterPOJOs register a POJO instance arr @os. The return value is @os's // mathching index array, in which "-1" means its matching POJO has been registered. func RegisterPOJOs(os ...POJO) []int { arr := make([]int, len(os)) for i := range os { arr[i] = RegisterPOJO(os[i]) } return arr } // RegisterJavaEnum Register a value type JavaEnum variable. func RegisterJavaEnum(o POJOEnum) int { var ( ok bool b []byte i int n int f string l []string t structInfo c ClassInfo v reflect.Value ) pojoRegistry.Lock() defer pojoRegistry.Unlock() if _, ok = pojoRegistry.registry[o.JavaClassName()]; !ok { v = reflect.ValueOf(o) switch v.Kind() { case reflect.Struct: t.typ = v.Type() case reflect.Ptr: t.typ = v.Elem().Type() default: t.typ = reflect.TypeOf(o) } t.goName = GetGoType(o) t.javaName = o.JavaClassName() t.inst = o pojoRegistry.j2g[t.javaName] = t.goName b = b[:0] b = encByte(b, BC_OBJECT_DEF) b = encString(b, t.javaName) l = l[:0] n = 1 b = encInt32(b, int32(n)) f = strings.ToLower("name") l = append(l, f) b = encString(b, f) c = ClassInfo{javaName: t.javaName, fieldNameList: l} c.buffer = append(c.buffer, b[:]...) t.index = len(pojoRegistry.classInfoList) pojoRegistry.classInfoList = append(pojoRegistry.classInfoList, &c) pojoRegistry.registry[t.goName] = &t i = t.index } else { i = -1 } return i } // check if go struct name @goName has been registered or not. func checkPOJORegistry(v interface{}) (int, bool) { s, ok := loadPOJORegistry(v) if !ok { return -1, false } return s.index, true } // load struct info if go struct name @goName has been registered or not. func loadPOJORegistry(v interface{}) (*structInfo, bool) { var ( ok bool s *structInfo ) goName := GetGoType(v) pojoRegistry.RLock() s, ok = pojoRegistry.registry[goName] pojoRegistry.RUnlock() return s, ok } // @typeName is class's java name func getStructInfo(javaName string) (*structInfo, bool) { pojoRegistry.RLock() defer pojoRegistry.RUnlock() if g, ok := pojoRegistry.j2g[javaName]; ok { s, b := pojoRegistry.registry[g] return s, b } return nil, false } func getStructDefByIndex(idx int) (reflect.Type, *ClassInfo, error) { var ( ok bool clsName string cls *ClassInfo s *structInfo ) pojoRegistry.RLock() defer pojoRegistry.RUnlock() if len(pojoRegistry.classInfoList) <= idx || idx < 0 { return nil, cls, perrors.Errorf("illegal class index @idx %d", idx) } cls = pojoRegistry.classInfoList[idx] clsName, ok = pojoRegistry.j2g[cls.javaName] if !ok { return nil, cls, perrors.Errorf("can not find java type name %s in registry", cls.javaName) } s, ok = pojoRegistry.registry[clsName] if !ok { return nil, cls, perrors.Errorf("can not find go type name %s in registry", clsName) } return s.typ, cls, nil } // Create a new instance by its struct name is @goName. // the return value is nil if @o has been registered. func createInstance(goName string) interface{} { var ( ok bool s *structInfo ) pojoRegistry.RLock() s, ok = pojoRegistry.registry[goName] pojoRegistry.RUnlock() if !ok { return nil } if s.typ.Kind() == reflect.Map { return reflect.MakeMap(s.typ).Interface() } return reflect.New(s.typ).Interface() } func lowerCamelCase(s string) string { runes := []rune(s) runes[0] = unicode.ToLower(runes[0]) return string(runes) } // buildMapClassDef build ClassInfo from map keys. func buildMapClassDef(javaName string, m map[string]interface{}) (*ClassInfo, error) { if javaName == "" { var ok bool javaName, ok = m[ClassKey].(string) if !ok { return nil, perrors.Errorf("no java name to build class info from map: %v", m) } } info := &ClassInfo{javaName: javaName} _, existClassKey := m[ClassKey] for fieldName := range m { if existClassKey && fieldName == ClassKey { continue } info.fieldNameList = append(info.fieldNameList, fieldName) } info.initDefBuffer() return info, nil }