blob: b27a3c6cfb0abbf59426c12b64fcd83a8d8955a3 [file] [log] [blame]
/*
* 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
)
// 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()
)
// 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 {
return pojoRegistry.registry[goName].index
}
// JavaClassName shouldn't equal to goName
if _, ok := pojoRegistry.registry[javaClassName]; ok {
return -1
}
var (
bHeader []byte
bBody []byte
fieldList []string
sttInfo structInfo
clsDef classInfo
)
sttInfo.typ = obtainValueType(o)
sttInfo.goName = sttInfo.typ.String()
sttInfo.javaName = javaClassName
sttInfo.inst = o
pojoRegistry.j2g[sttInfo.javaName] = sttInfo.goName
registerTypeName(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 := obtainValueType(o).String()
if structInfo, ok := pojoRegistry.registry[goName]; ok {
delete(pojoRegistry.j2g, structInfo.javaName)
listTypeNameMapper.Delete(structInfo.goName)
// remove registry cache.
delete(pojoRegistry.registry, structInfo.goName)
// don't remove registry classInfoList,
// indexes of registered pojo may be affected.
return structInfo.index
}
return -1
}
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 = t.typ.String()
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(goName string) (int, bool) {
s, ok := loadPOJORegistry(goName)
if !ok {
return -1, false
}
return s.index, true
}
// load struct info if go struct name @goName has been registered or not.
func loadPOJORegistry(goName string) (*structInfo, bool) {
var (
ok bool
s *structInfo
)
pojoRegistry.RLock()
s, ok = pojoRegistry.registry[goName]
pojoRegistry.RUnlock()
return s, ok
}
// @typeName is class's java name
func getStructInfo(javaName string) (*structInfo, bool) {
var (
ok bool
g string
s *structInfo
)
pojoRegistry.RLock()
g, ok = pojoRegistry.j2g[javaName]
if ok {
s, ok = pojoRegistry.registry[g]
}
pojoRegistry.RUnlock()
return s, ok
}
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)
}