blob: 5f7c31e019ff6765f083bb11c9dc01d3a65cc8d8 [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
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
}