| /* |
| * 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) |
| } |