blob: de9dc16ca81e7d1956346f6a07a8eca5152348db [file] [log] [blame]
// Copyright 2016-2019 Alex Stocks
//
// 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 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 {
// # definition for an object (compact map)
// class-def ::= 'C' string int string*
pojoRegistry.Lock()
defer pojoRegistry.Unlock()
if _, ok := pojoRegistry.registry[o.JavaClassName()]; ok {
return -1
}
var (
bHeader []byte
bBody []byte
fieldList []string
structInfo structInfo
clsDef classInfo
v reflect.Value
)
v = reflect.ValueOf(o)
switch v.Kind() {
case reflect.Struct:
structInfo.typ = v.Type()
case reflect.Ptr:
structInfo.typ = v.Elem().Type()
default:
structInfo.typ = reflect.TypeOf(o)
}
structInfo.goName = structInfo.typ.String()
structInfo.javaName = o.JavaClassName()
structInfo.inst = o
pojoRegistry.j2g[structInfo.javaName] = structInfo.goName
registerTypeName(structInfo.goName, structInfo.javaName)
// prepare fields info of objectDef
for i := 0; i < structInfo.typ.NumField(); i++ {
// skip unexported anonymous filed
if structInfo.typ.Field(i).PkgPath != "" {
continue
}
var fieldName string
if val, has := structInfo.typ.Field(i).Tag.Lookup(tagIdentifier); has {
fieldName = val
} else {
fieldName = lowerCamelCase(structInfo.typ.Field(i).Name)
}
fieldList = append(fieldList, fieldName)
bBody = encString(bBody, fieldName)
}
// prepare header of objectDef
bHeader = encByte(bHeader, BC_OBJECT_DEF)
bHeader = encString(bHeader, structInfo.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: structInfo.javaName, fieldNameList: fieldList}
// merge header and body of objectDef into buffer of classInfo
clsDef.buffer = append(bHeader, bBody...)
structInfo.index = len(pojoRegistry.classInfoList)
pojoRegistry.classInfoList = append(pojoRegistry.classInfoList, clsDef)
pojoRegistry.registry[structInfo.goName] = structInfo
return structInfo.index
}
// 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) {
var (
ok bool
s structInfo
)
pojoRegistry.RLock()
s, ok = pojoRegistry.registry[goName]
pojoRegistry.RUnlock()
return s.index, 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
}
return reflect.New(s.typ).Interface()
}
func lowerCamelCase(s string) string {
runes := []rune(s)
runes[0] = unicode.ToLower(runes[0])
return string(runes)
}