blob: 1b41728a462dae0d0bd8e9839ae6ee0d11ad5e5f [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.
import ctypes
from typing import Any
from pyignite.constants import *
from . import Null
from .base import IgniteDataType
from .primitive import *
from .type_codes import *
from .type_ids import *
from .type_names import *
__all__ = [
'ByteArray', 'ByteArrayObject', 'ShortArray', 'ShortArrayObject',
'IntArray', 'IntArrayObject', 'LongArray', 'LongArrayObject',
'FloatArray', 'FloatArrayObject', 'DoubleArray', 'DoubleArrayObject',
'CharArray', 'CharArrayObject', 'BoolArray', 'BoolArrayObject',
]
class PrimitiveArray(IgniteDataType):
"""
Base class for array of primitives. Payload-only.
"""
_type_name = None
_type_id = None
primitive_type = None
type_code = None
@staticmethod
def hashcode(value: Any) -> int:
# Arrays are not supported as keys at the moment.
return 0
@classmethod
def build_header_class(cls):
return type(
cls.__name__+'Header',
(ctypes.LittleEndianStructure,),
{
'_pack_': 1,
'_fields_': [
('length', ctypes.c_int),
],
}
)
@classmethod
def parse(cls, client: 'Client'):
tc_type = client.recv(ctypes.sizeof(ctypes.c_byte))
if tc_type == TC_NULL:
return Null.build_c_type(), tc_type
header_class = cls.build_header_class()
buffer = tc_type + client.recv(ctypes.sizeof(header_class) - len(tc_type))
header = header_class.from_buffer_copy(buffer)
final_class = type(
cls.__name__,
(header_class,),
{
'_pack_': 1,
'_fields_': [
('data', cls.primitive_type.c_type * header.length),
],
}
)
buffer += client.recv(
ctypes.sizeof(final_class) - ctypes.sizeof(header_class)
)
return final_class, buffer
@classmethod
def to_python(cls, ctype_object, *args, **kwargs):
result = []
length = getattr(ctype_object, "length", None)
if length is None:
return None
for i in range(length):
result.append(ctype_object.data[i])
return result
@classmethod
def from_python(cls, value):
if value is None:
return Null.from_python()
header_class = cls.build_header_class()
header = header_class()
if hasattr(header, 'type_code'):
header.type_code = int.from_bytes(
cls.type_code,
byteorder=PROTOCOL_BYTE_ORDER
)
length = len(value)
header.length = length
buffer = bytearray(header)
for x in value:
buffer += cls.primitive_type.from_python(x)
return bytes(buffer)
class ByteArray(PrimitiveArray):
_type_name = NAME_BYTE_ARR
_type_id = TYPE_BYTE_ARR
primitive_type = Byte
type_code = TC_BYTE_ARRAY
@classmethod
def to_python(cls, ctype_object, *args, **kwargs):
data = getattr(ctype_object, "data", None)
if data is None:
return None
return bytearray(data)
@classmethod
def from_python(cls, value):
header_class = cls.build_header_class()
header = header_class()
# no need to iterate on bytes or bytearray
# to create ByteArray data buffer
header.length = len(value)
return bytes(bytearray(header) + bytearray(value))
class ShortArray(PrimitiveArray):
_type_name = NAME_SHORT_ARR
_type_id = TYPE_SHORT_ARR
primitive_type = Short
type_code = TC_SHORT_ARRAY
class IntArray(PrimitiveArray):
_type_name = NAME_INT_ARR
_type_id = TYPE_INT_ARR
primitive_type = Int
type_code = TC_INT_ARRAY
class LongArray(PrimitiveArray):
_type_name = NAME_LONG_ARR
_type_id = TYPE_LONG_ARR
primitive_type = Long
type_code = TC_LONG_ARRAY
class FloatArray(PrimitiveArray):
_type_name = NAME_FLOAT_ARR
_type_id = TYPE_FLOAT_ARR
primitive_type = Float
type_code = TC_FLOAT_ARRAY
class DoubleArray(PrimitiveArray):
_type_name = NAME_DOUBLE_ARR
_type_id = TYPE_DOUBLE_ARR
primitive_type = Double
type_code = TC_DOUBLE_ARRAY
class CharArray(PrimitiveArray):
_type_name = NAME_CHAR_ARR
_type_id = TYPE_CHAR_ARR
primitive_type = Char
type_code = TC_CHAR_ARRAY
class BoolArray(PrimitiveArray):
_type_name = NAME_BOOLEAN_ARR
_type_id = TYPE_BOOLEAN_ARR
primitive_type = Bool
type_code = TC_BOOL_ARRAY
class PrimitiveArrayObject(PrimitiveArray):
"""
Base class for primitive array object. Type code plus payload.
"""
_type_name = None
_type_id = None
pythonic = list
default = []
@classmethod
def build_header_class(cls):
return type(
cls.__name__+'Header',
(ctypes.LittleEndianStructure,),
{
'_pack_': 1,
'_fields_': [
('type_code', ctypes.c_byte),
('length', ctypes.c_int),
],
}
)
class ByteArrayObject(PrimitiveArrayObject):
_type_name = NAME_BYTE_ARR
_type_id = TYPE_BYTE_ARR
primitive_type = Byte
type_code = TC_BYTE_ARRAY
@classmethod
def to_python(cls, ctype_object, *args, **kwargs):
return ByteArray.to_python(ctype_object, *args, **kwargs)
@classmethod
def from_python(cls, value):
if value is None:
return Null.from_python()
header_class = cls.build_header_class()
header = header_class()
header.type_code = int.from_bytes(
cls.type_code,
byteorder=PROTOCOL_BYTE_ORDER
)
# no need to iterate on bytes or bytearray
# to create ByteArrayObject data buffer
header.length = len(value)
try:
# `value` is a `bytearray` or a sequence of integer values
# in range 0 to 255
value_buffer = bytearray(value)
except ValueError:
# `value` is a sequence of integers in range -128 to 127
value_buffer = bytearray()
for ch in value:
if -128 <= ch <= 255:
value_buffer.append(ctypes.c_ubyte(ch).value)
else:
raise ValueError(
'byte must be in range(-128, 256)!'
) from None
return bytes(bytearray(header) + value_buffer)
class ShortArrayObject(PrimitiveArrayObject):
_type_name = NAME_SHORT_ARR
_type_id = TYPE_SHORT_ARR
primitive_type = Short
type_code = TC_SHORT_ARRAY
class IntArrayObject(PrimitiveArrayObject):
_type_name = NAME_INT_ARR
_type_id = TYPE_INT_ARR
primitive_type = Int
type_code = TC_INT_ARRAY
class LongArrayObject(PrimitiveArrayObject):
_type_name = NAME_LONG_ARR
_type_id = TYPE_LONG_ARR
primitive_type = Long
type_code = TC_LONG_ARRAY
class FloatArrayObject(PrimitiveArrayObject):
_type_name = NAME_FLOAT_ARR
_type_id = TYPE_FLOAT_ARR
primitive_type = Float
type_code = TC_FLOAT_ARRAY
class DoubleArrayObject(PrimitiveArrayObject):
_type_name = NAME_DOUBLE_ARR
_type_id = TYPE_DOUBLE_ARR
primitive_type = Double
type_code = TC_DOUBLE_ARRAY
class CharArrayObject(PrimitiveArrayObject):
_type_name = NAME_CHAR_ARR
_type_id = TYPE_CHAR_ARR
primitive_type = Char
type_code = TC_CHAR_ARRAY
@classmethod
def to_python(cls, ctype_object, *args, **kwargs):
values = super().to_python(ctype_object, *args, **kwargs)
if values is None:
return None
return [
v.to_bytes(
ctypes.sizeof(cls.primitive_type.c_type),
byteorder=PROTOCOL_BYTE_ORDER
).decode(
PROTOCOL_CHAR_ENCODING
) for v in values
]
class BoolArrayObject(PrimitiveArrayObject):
_type_name = NAME_BOOLEAN_ARR
_type_id = TYPE_BOOLEAN_ARR
primitive_type = Bool
type_code = TC_BOOL_ARRAY
@classmethod
def to_python(cls, ctype_object, *args, **kwargs):
if not ctype_object:
return None
length = getattr(ctype_object, "length", None)
if length is None:
return None
result = [False] * length
for i in range(length):
result[i] = ctype_object.data[i] != 0
return result