<filename>dubbo/codec/encoder.py
# -*- coding: utf-8 -*-
"""
/*
 * 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.
 */
"""
import calendar
import datetime
import struct
from typing import Mapping, Union

from dubbo.common.constants import MIN_INT_32, MAX_INT_32, DEFAULT_REQUEST_META
from dubbo.common.exceptions import HessianTypeError
from dubbo.common.util import double_to_long_bits, get_invoke_id

"""
把Python的数据结构根据Hessian协议序列化为相应的字节数组
当前支持的数据类型：
* bool
* int
* long
* float
* double
* java.lang.String
* java.lang.Object
"""


class Object(object):
    """
    创建一个Java对象
    """

    def __init__(self, path, values=None, field_meta: Mapping[str, str] = None):
        """
        :param path:   Java对象的路径，例如：java.lang.Object
        :param values: 可以在创建对象时就进行赋值
        :param field_meta: java class meta for field
        """
        if not isinstance(path, str):
            raise ValueError('Object path {} should be string type.'.format(path))
        self.__path = path
        if not isinstance(values, dict):
            values = {}
        if not field_meta or not isinstance(field_meta, dict):
            field_meta = {}
        self.__values = values
        self.__field_meta = field_meta

    def __getitem__(self, key):
        return self.__values[key]

    def __setitem__(self, key, value):
        if not isinstance(key, (str)):
            raise ValueError('Object key {} should be string type.'.format(key))
        self.__values[key] = value

    def __delitem__(self, key):
        del self.__values[key]

    def __repr__(self):
        return '<java object {} at {} with {}>'.format(self.__path, hex(id(self)), self.__values)

    def __contains__(self, key):
        return key in self.__values

    def keys(self):
        return self.__values.keys()

    def get_path(self):
        return self.__path

    def has_meta(self, key):
        return key in self.__field_meta.keys()

    def get_meta(self, key):
        return self.__field_meta.get(key)


class BigDecimal(Object):
    def __init__(self, value=Union[str, float, None]):
        """
        java.math.BigDecimal
        """
        super().__init__(path='java.math.BigDecimal', values={'value': str(value) if value is not None else None})


class BigInteger(Object):
    def __init__(self, value=Union[str, int]):
        """
        java.math.BigInt
        """
        super().__init__(path='java.math.BigInteger', values={'value': str(value) if value is not None else None})


class Request(object):
    """
    A class for dumping dubbo request body.
    All types can be dumped:
    * boolean
    * int
    * long
    * double
    * string
    * object
    """

    def __init__(self, request):
        self.__body = request
        self.__classes = []
        self.types = []  # 泛型
        self.invoke_id = get_invoke_id()

    def encode(self):
        """
        把请求序列化为字节数组
        :return:
        """
        request_body = self._encode_request_body()
        invoke_id = list(bytearray(struct.pack('!q', self.invoke_id)))
        request_head = DEFAULT_REQUEST_META + invoke_id + get_request_body_length(request_body)
        return bytearray(request_head + request_body)

    def _get_parameter_types(self, arguments):
        """
        针对所有的参数计算得到参数类型字符串
        :param arguments:
        :return:
        """
        parameter_types = ''
        # 判断并得出参数的类型
        for argument in arguments:
            parameter_types += self._get_class_name(argument)
        return parameter_types

    def _get_class_name(self, _class):
        """
        根据一个字段的类型得到其在Java中对应类的全限定名
        转换规则：https://stackoverflow.com/a/3442100/4614538
        :param _class:
        :return:
        """
        if isinstance(_class, bool):  # bool类型的判断必须放在int类型判断的前面
            return 'Z'
        elif isinstance(_class, int):
            if MIN_INT_32 <= _class <= MAX_INT_32:
                return 'I'
            else:
                return 'J'
        elif isinstance(_class, float):
            return 'D'
        elif isinstance(_class, (str)):
            return 'L' + 'java/lang/String' + ';'
        elif isinstance(_class, Object):
            path = _class.get_path()
            path = 'L' + path.replace('.', '/') + ';'
            return path
        elif isinstance(_class, list):
            if len(_class) == 0:
                raise HessianTypeError('Method parameter {} is a list but length is zero'.format(_class))
            return '[' + self._get_class_name(_class[0])
        else:
            raise HessianTypeError('Unknown argument type: {0}'.format(_class))

    def _encode_request_body(self):
        """
        对所有的已知的参数根据dubbo协议进行编码
        :return:
        """
        dubbo_version = self.__body.get('dubbo_version')
        path = self.__body.get('path')
        version = self.__body.get('version')
        method = self.__body.get('method')
        arguments = self.__body.get('arguments')
        group = self.__body.get('group')

        body = []
        body.extend(self._encode_single_value(dubbo_version))
        body.extend(self._encode_single_value(path))
        body.extend(self._encode_single_value(version))
        body.extend(self._encode_single_value(method))
        body.extend(self._encode_single_value(self._get_parameter_types(arguments)))
        for argument in arguments:
            body.extend(self._encode_single_value(argument))
        attachments = {
            'path': path,
            'interface': path,
            'version': version,
        }
        if group:
            attachments.update({'group': group})
        # attachments参数以H开头，以Z结尾
        body.append(ord('H'))
        for key in attachments.keys():
            value = attachments[key]
            body.extend(self._encode_single_value(key))
            body.extend(self._encode_single_value(value))
        body.append(ord('Z'))

        # 因为在上面的逻辑中没有对byte大小进行检测，所以在这里进行统一的处理
        for i in range(len(body)):
            body[i] = body[i] & 0xff
        return body

    @staticmethod
    def _encode_bool(value):
        """
        对bool类型进行编码
        :param value:
        :return:
        """
        result = []
        if value:
            result.append(ord('T'))
        else:
            result.append(ord('F'))
        return result

    @staticmethod
    def _encode_int(value):
        """
        对整数进行编码
        :param value:
        :return:
        """
        result = []
        # 超出int类型范围的值则转化为long类型
        # 这里问题在于对于落在int范围内的数字，我们无法判断其是long类型还是int类型，所以一律认为其是int类型
        if value > MAX_INT_32 or value < MIN_INT_32:
            result.append(ord('L'))
            result.extend(list(bytearray(struct.pack('!q', value))))
            return result

        if -0x10 <= value <= 0x2f:
            result.append(value + 0x90)
        elif -0x800 <= value <= 0x7ff:
            result.append(0xc8 + (value >> 8))
            result.append(value)
        elif -0x40000 <= value <= 0x3ffff:
            result.append(0xd4 + (value >> 16))
            result.append(value >> 8)
            result.append(value)
        else:
            result.append(ord('I'))
            result.append(value >> 24)
            result.append(value >> 16)
            result.append(value >> 8)
            result.append(value)
        return result

    @staticmethod
    def _encode_datetime(value):
        result = []
        result.extend(list(bytearray(struct.pack('>cq', b'd', int(calendar.timegm(value.timetuple())) * 1000))))
        return result

    @staticmethod
    def _encode_long(value):
        result = []
        result.extend(list(bytearray(struct.pack('>cq', b'L', value))))
        return result

    @staticmethod
    def _encode_float(value):
        """
        对浮点类型进行编码
        :param value:
        :return:
        """
        result = []
        int_value = int(value)
        if int_value == value:
            if int_value == 0:
                result.append(0x5b)
                return result
            elif int_value == 1:
                result.append(0x5c)
                return result
            elif -0x80 <= int_value < 0x80:
                result.append(0x5d)
                result.append(int_value)
                return result
            elif -0x8000 <= int_value < 0x8000:
                result.append(0x5e)
                result.append(int_value >> 8)
                result.append(int_value)
                return result

        mills = int(value * 1000)
        if 0.001 * mills == value and MIN_INT_32 <= mills <= MAX_INT_32:
            result.append(0x5f)
            result.append(mills >> 24)
            result.append(mills >> 16)
            result.append(mills >> 8)
            result.append(mills)
            return result

        bits = double_to_long_bits(value)
        result.append(ord('D'))
        result.append(bits >> 56)
        result.append(bits >> 48)
        result.append(bits >> 40)
        result.append(bits >> 32)
        result.append(bits >> 24)
        result.append(bits >> 16)
        result.append(bits >> 8)
        result.append(bits)
        return result

    @staticmethod
    def _encode_utf(value):
        """
        对字符串进行编码，编码格式utf-8
        参见方法：com.alibaba.com.caucho.hessian.io.Hessian2Output#printString
        :param value:
        :return:
        """
        result = []
        for v in value:
            ch = v
            if ch < 0x80:
                result.append(ch & 0xff)
            elif ch < 0x800:
                result.append((0xc0 + ((ch >> 6) & 0x1f)) & 0xff)
                result.append((0x80 + (ch & 0x3f)) & 0xff)
            else:
                result.append((0xe0 + ((ch >> 12) & 0xf)) & 0xff)
                result.append((0x80 + ((ch >> 6) & 0x3f)) & 0xff)
                result.append((0x80 + (ch & 0x3f)) & 0xff)
        return result

    def _encode_str(self, value):
        """
        对一个字符串进行编码
        :param value:
        :return:
        """
        result = []
        # 在进行网络传输操作时一律使用unicode进行操作
        if isinstance(value, str):
            value = value.encode('utf-8')
        length = len(value)
        if length <= 0x1f:
            result.append(0x00 + length)
        elif length <= 0x3ff:
            result.append(0x30 + (length >> 8))
            result.append(length)
        else:
            result.append(ord('S'))
            result.append(length >> 8)
            result.append(length)

        result.extend(self._encode_utf(value))
        return result

    def _encode_object(self, value):
        """
        对一个对象进行编码
        :param value:
        :return:
        """
        result = []
        path = value.get_path()
        field_names = value.keys()

        if path not in self.__classes:
            result.append(ord('C'))
            result.extend(self._encode_single_value(path))

            result.extend(self._encode_single_value(len(field_names)))

            for field_name in field_names:
                result.extend(self._encode_single_value(field_name))
            self.__classes.append(path)
        class_id = self.__classes.index(path)
        if class_id <= 0xf:
            class_id += 0x60
            class_id &= 0xff
            result.append(class_id)
        else:
            result.append(ord('O'))
            result.extend(self._encode_single_value(class_id))
        for field_name in field_names:
            if value.has_meta(field_name):
                field_meta = value.get_meta(field_name)
                if field_meta == 'java.math.BigDecimal':
                    result.extend(self._encode_map_object(BigDecimal(value[field_name])))
                if field_meta == 'java.math.BigInteger':
                    result.extend(self._encode_map_object(BigInteger(value[field_name])))
                if field_meta == 'long':
                    result.extend(self._encode_long(int(value[field_name])))
            else:
                result.extend(self._encode_single_value(value[field_name]))
        return result

    def _encode_map_object(self, value: Object):
        result = []
        result.extend(list(bytearray(struct.pack('!c', b'M'))))
        result.extend(self._encode_single_value(value.get_path()))
        result.extend(self._encode_single_value('value'))
        result.extend(self._encode_single_value(str(value['value'])))
        result.extend(list(bytearray(struct.pack('!c', b'Z'))))
        return result

    def _encode_list(self, value):
        """
        对一个列表进行编码
        :param value:
        :return:
        """
        result = []
        length = len(value)
        if length == 0:
            # 没有值则无法判断类型，一律返回null
            return self._encode_single_value(None)
        if isinstance(value[0], bool):
            _type = '[boolean'
        elif isinstance(value[0], int):
            _type = '[int'
        elif isinstance(value[0], float):
            _type = '[double'
        elif isinstance(value[0], str):
            _type = '[string'
        elif isinstance(value[0], Object):
            _type = '[object'
        else:
            raise HessianTypeError('Unknown list type: {}'.format(value[0]))
        if length < 0x7:
            result.append(0x70 + length)
            if _type not in self.types:
                self.types.append(_type)
                result.extend(self._encode_single_value(_type))
            else:
                result.extend(self._encode_single_value(self.types.index(_type)))
        else:
            result.append(0x56)
            if _type not in self.types:
                self.types.append(_type)
                result.extend(self._encode_single_value(_type))
            else:
                result.extend(self._encode_single_value(self.types.index(_type)))
            result.extend(self._encode_single_value(length))
        for v in value:
            if type(value[0]) != type(v):
                raise HessianTypeError('All elements in list must be the same type, first type'
                                       ' is {0} but current type is {1}'.format(type(value[0]), type(v)))
            result.extend(self._encode_single_value(v))
        return result

    def _encode_single_value(self, value):
        """
        根据hessian协议对单个变量进行编码
        :param value:
        :return:
        """
        # 布尔类型
        if isinstance(value, bool):
            return self._encode_bool(value)
        # 整型（包括长整型）
        elif isinstance(value, int):
            return self._encode_int(value)
        # 浮点类型
        elif isinstance(value, float):
            return self._encode_float(value)
        # 字符串类型
        elif isinstance(value, str):
            return self._encode_str(value)
        elif isinstance(value, datetime.datetime):
            return self._encode_datetime(value)
        # 对象类型
        elif isinstance(value, Object):
            return self._encode_object(value)
        # 列表(list)类型，不可以使用tuple替代
        elif isinstance(value, list):
            return self._encode_list(value)
        # null
        elif value is None:
            return [ord('N')]
        else:
            raise HessianTypeError('Unknown argument type: {}'.format(value))


def get_request_body_length(body):
    """
    获取body的长度，并将其转为长度为4个字节的字节数组
    :param body:
    :return:
    """
    return list(bytearray(struct.pack('!i', len(body))))


if __name__ == '__main__':
    pass
