• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tcalmant / python-javaobj / 8591112367

07 Apr 2024 07:18PM UTC coverage: 78.701%. First build
8591112367

push

github

tcalmant
Added 3.11 & 3.12 to GitHub actions

1611 of 2047 relevant lines covered (78.7%)

4.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.4
/javaobj/v2/transformers.py
1
#!/usr/bin/env python3
2
"""
6✔
3
Defines the default object transformers
4

5
:authors: Thomas Calmant
6
:license: Apache License 2.0
7
:version: 0.4.4
8
:status: Alpha
9

10
..
11

12
    Copyright 2024 Thomas Calmant
13

14
    Licensed under the Apache License, Version 2.0 (the "License");
15
    you may not use this file except in compliance with the License.
16
    You may obtain a copy of the License at
17

18
        http://www.apache.org/licenses/LICENSE-2.0
19

20
    Unless required by applicable law or agreed to in writing, software
21
    distributed under the License is distributed on an "AS IS" BASIS,
22
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23
    See the License for the specific language governing permissions and
24
    limitations under the License.
25
"""
26

27
# Standard library
28
import functools
6✔
29
from typing import List, Optional, Tuple
6✔
30

31
# Numpy (optional)
32
try:
6✔
33
    import numpy
6✔
34
except ImportError:
6✔
35
    numpy = None  # type: ignore
6✔
36

37
# Javaobj
38
from ..constants import TerminalCode, TypeCode
6✔
39
from ..utils import log_debug, log_error, read_string, read_struct, to_bytes
6✔
40
from .api import IJavaStreamParser, ObjectTransformer
6✔
41
from .beans import ( # pylint:disable=W0611
6✔
42
    BlockData,
43
    JavaClassDesc,
44
    JavaInstance,
45
)
46
from .stream import DataStreamReader
6✔
47

48
# ------------------------------------------------------------------------------
49

50
# Module version
51
__version_info__ = (0, 4, 4)
6✔
52
__version__ = ".".join(str(x) for x in __version_info__)
6✔
53

54
# Documentation strings format
55
__docformat__ = "restructuredtext en"
6✔
56

57
# ------------------------------------------------------------------------------
58

59

60
class JavaList(list, JavaInstance):
6✔
61
    """
62
    Python-Java list bridge type
63
    """
64

65
    HANDLED_CLASSES = ("java.util.ArrayList", "java.util.LinkedList")
6✔
66

67
    def __init__(self):
6✔
68
        list.__init__(self)
6✔
69
        JavaInstance.__init__(self)
6✔
70

71
    def load_from_instance(self, indent=0):
6✔
72
        # type: (int) -> bool
73
        """
74
        Load content from a parsed instance object
75
        """
76
        # Lists have their content in there annotations
77
        for cd, annotations in self.annotations.items():
6✔
78
            if cd.name in self.HANDLED_CLASSES:
6✔
79
                self.extend(ann for ann in annotations[1:])
6✔
80
                return True
6✔
81

82
        return False
×
83

84

85
@functools.total_ordering
6✔
86
class JavaPrimitiveClass(JavaInstance):
6✔
87
    """
88
    Parent of Java classes matching a primitive (Bool, Integer, Long, ...)
89
    """
90

91
    def __init__(self):
6✔
92
        JavaInstance.__init__(self)
6✔
93
        self.value = None
6✔
94

95
    def __str__(self):
6✔
96
        return str(self.value)
6✔
97

98
    def __repr__(self):
6✔
99
        return repr(self.value)
×
100

101
    def __hash__(self):
6✔
102
        return hash(self.value)
6✔
103

104
    def __eq__(self, other):
6✔
105
        return self.value == other
6✔
106

107
    def __lt__(self, other):
6✔
108
        return self.value < other
×
109

110
    def load_from_instance(self, indent=0):
6✔
111
        # type: (int) -> bool
112
        """
113
        Load content from a parsed instance object
114
        """
115
        for fields in self.field_data.values():
6✔
116
            for field, value in fields.items():
6✔
117
                if field.name == "value":
6✔
118
                    self.value = value
6✔
119
                    return True
6✔
120

121
        return False
×
122

123

124
class JavaBool(JavaPrimitiveClass):
6✔
125
    """
126
    Represents a Java Boolean object
127
    """
128

129
    HANDLED_CLASSES = "java.lang.Boolean"
6✔
130

131
    def __bool__(self):
6✔
132
        return self.value
×
133

134

135
class JavaInt(JavaPrimitiveClass):
6✔
136
    """
137
    Represents a Java Integer or Long object
138
    """
139

140
    HANDLED_CLASSES = ("java.lang.Integer", "java.lang.Long")
6✔
141

142
    def __int__(self):
6✔
143
        return self.value
×
144

145

146
class JavaMap(dict, JavaInstance):
6✔
147
    """
148
    Python-Java dictionary/map bridge type
149
    """
150

151
    HANDLED_CLASSES = (
6✔
152
        "java.util.HashMap",
153
        "java.util.TreeMap",
154
    )  # type: Tuple[str, ...]
155

156
    def __init__(self):
6✔
157
        dict.__init__(self)
6✔
158
        JavaInstance.__init__(self)
6✔
159

160
    def load_from_instance(self, indent=0):
6✔
161
        # type: (int) -> bool
162
        """
163
        Load content from a parsed instance object
164
        """
165
        # Maps have their content in there annotations
166
        for cd, annotations in self.annotations.items():
6✔
167
            if cd.name in JavaMap.HANDLED_CLASSES:
6✔
168
                # Group annotation elements 2 by 2
169
                args = [iter(annotations[1:])] * 2
6✔
170
                for key, value in zip(*args):
6✔
171
                    self[key] = value
6✔
172

173
                return True
6✔
174

175
        return False
×
176

177

178
class JavaLinkedHashMap(JavaMap):
6✔
179
    """
180
    Linked has map are handled with a specific block data
181
    """
182

183
    HANDLED_CLASSES = ("java.util.LinkedHashMap",)
6✔
184

185
    def load_from_blockdata(self, parser, reader, indent=0):
6✔
186
        # type: (IJavaStreamParser, DataStreamReader, int) -> bool
187
        """
188
        Loads the content of the map, written with a custom implementation
189
        """
190
        # Read HashMap fields
191
        self.buckets = reader.read_int()
×
192
        self.size = reader.read_int()
×
193

194
        # Read entries
195
        for _ in range(self.size):
×
196
            key_code = reader.read_byte()
×
197
            key = parser._read_content(key_code, True)
×
198

199
            value_code = reader.read_byte()
×
200
            value = parser._read_content(value_code, True)
×
201
            self[key] = value
×
202

203
        # Ignore the end of the blockdata
204
        type_code = reader.read_byte()
×
205
        if type_code != TerminalCode.TC_ENDBLOCKDATA:
×
206
            raise ValueError("Didn't find the end of block data")
×
207

208
        # Ignore the trailing 0
209
        final_byte = reader.read_byte()
×
210
        if final_byte != 0:
×
211
            raise ValueError("Should find 0x0, got {0:x}".format(final_byte))
×
212

213
        return True
×
214

215

216
class JavaSet(set, JavaInstance):
6✔
217
    """
218
    Python-Java set bridge type
219
    """
220

221
    HANDLED_CLASSES = (
6✔
222
        "java.util.HashSet",
223
        "java.util.LinkedHashSet",
224
    )  # type: Tuple[str, ...]
225

226
    def __init__(self):
6✔
227
        set.__init__(self)
6✔
228
        JavaInstance.__init__(self)
6✔
229

230
    def load_from_instance(self, indent=0):
6✔
231
        # type: (int) -> bool
232
        """
233
        Load content from a parsed instance object
234
        """
235
        # Lists have their content in there annotations
236
        for cd, annotations in self.annotations.items():
6✔
237
            if cd.name in self.HANDLED_CLASSES:
6✔
238
                self.update(x for x in annotations[1:])
6✔
239
                return True
6✔
240

241
        return False
×
242

243

244
class JavaTreeSet(JavaSet):
6✔
245
    """
246
    Tree sets are handled a bit differently
247
    """
248

249
    HANDLED_CLASSES = ("java.util.TreeSet",)
6✔
250

251
    def load_from_instance(self, indent=0):
6✔
252
        # type: (int) -> bool
253
        """
254
        Load content from a parsed instance object
255
        """
256
        # Lists have their content in there annotations
257
        for cd, annotations in self.annotations.items():
6✔
258
            if cd.name in self.HANDLED_CLASSES:
6✔
259
                # Annotation[1] == size of the set
260
                self.update(x for x in annotations[2:])
6✔
261
                return True
6✔
262

263
        return False
×
264

265

266
class JavaTime(JavaInstance):
6✔
267
    """
268
    Represents the classes found in the java.time package
269

270
    The semantic of the fields depends on the type of time that has been
271
    parsed
272
    """
273

274
    HANDLED_CLASSES = ("java.time.Ser",)  # type: Tuple[str, ...]
6✔
275

276
    DURATION_TYPE = 1
6✔
277
    INSTANT_TYPE = 2
6✔
278
    LOCAL_DATE_TYPE = 3
6✔
279
    LOCAL_TIME_TYPE = 4
6✔
280
    LOCAL_DATE_TIME_TYPE = 5
6✔
281
    ZONE_DATE_TIME_TYPE = 6
6✔
282
    ZONE_REGION_TYPE = 7
6✔
283
    ZONE_OFFSET_TYPE = 8
6✔
284
    OFFSET_TIME_TYPE = 9
6✔
285
    OFFSET_DATE_TIME_TYPE = 10
6✔
286
    YEAR_TYPE = 11
6✔
287
    YEAR_MONTH_TYPE = 12
6✔
288
    MONTH_DAY_TYPE = 13
6✔
289
    PERIOD_TYPE = 14
6✔
290

291
    def __init__(self):
6✔
292
        JavaInstance.__init__(self)
6✔
293
        self.type = -1
6✔
294
        self.year = None
6✔
295
        self.month = None
6✔
296
        self.day = None
6✔
297
        self.hour = None
6✔
298
        self.minute = None
6✔
299
        self.second = None
6✔
300
        self.nano = None
6✔
301
        self.offset = None
6✔
302
        self.zone = None
6✔
303

304
        self.time_handlers = {
6✔
305
            self.DURATION_TYPE: self.do_duration,
306
            self.INSTANT_TYPE: self.do_instant,
307
            self.LOCAL_DATE_TYPE: self.do_local_date,
308
            self.LOCAL_DATE_TIME_TYPE: self.do_local_date_time,
309
            self.LOCAL_TIME_TYPE: self.do_local_time,
310
            self.ZONE_DATE_TIME_TYPE: self.do_zoned_date_time,
311
            self.ZONE_OFFSET_TYPE: self.do_zone_offset,
312
            self.ZONE_REGION_TYPE: self.do_zone_region,
313
            self.OFFSET_TIME_TYPE: self.do_offset_time,
314
            self.OFFSET_DATE_TIME_TYPE: self.do_offset_date_time,
315
            self.YEAR_TYPE: self.do_year,
316
            self.YEAR_MONTH_TYPE: self.do_year_month,
317
            self.MONTH_DAY_TYPE: self.do_month_day,
318
            self.PERIOD_TYPE: self.do_period,
319
        }
320

321
    def __str__(self):
6✔
322
        return (
6✔
323
            "JavaTime(type=0x{s.type}, "
324
            "year={s.year}, month={s.month}, day={s.day}, "
325
            "hour={s.hour}, minute={s.minute}, second={s.second}, "
326
            "nano={s.nano}, offset={s.offset}, zone={s.zone})"
327
        ).format(s=self)
328

329
    def load_from_blockdata(self, parser, reader, indent=0):
6✔
330
        """
331
        Ignore the SC_BLOCK_DATA flag
332
        """
333
        return True
×
334

335
    def load_from_instance(self, indent=0):
6✔
336
        # type: (int) -> bool
337
        """
338
        Load content from a parsed instance object
339
        """
340
        # Lists have their content in there annotations
341
        for cd, annotations in self.annotations.items():
6✔
342
            if cd.name in self.HANDLED_CLASSES:
6✔
343
                if not isinstance(annotations[0], BlockData):
6✔
344
                    raise ValueError("Require a BlockData as annotation")
×
345

346
                # Convert back annotations to bytes
347
                # latin-1 is used to ensure that bytes are kept as is
348
                content = to_bytes(annotations[0].data, "latin1")
6✔
349
                (self.type,), content = read_struct(content, ">b")
6✔
350

351
                try:
6✔
352
                    self.time_handlers[self.type](content)
6✔
353
                except KeyError as ex:
×
354
                    log_error("Unhandled kind of time: {}".format(ex))
×
355

356
                return True
6✔
357

358
        return False
×
359

360
    def do_duration(self, data):
6✔
361
        (self.second, self.nano), data = read_struct(data, ">qi")
6✔
362
        return data
6✔
363

364
    def do_instant(self, data):
6✔
365
        (self.second, self.nano), data = read_struct(data, ">qi")
6✔
366
        return data
6✔
367

368
    def do_local_date(self, data):
6✔
369
        (self.year, self.month, self.day), data = read_struct(data, ">ibb")
6✔
370
        return data
6✔
371

372
    def do_local_time(self, data):
6✔
373
        (hour,), data = read_struct(data, ">b")
6✔
374
        minute = 0
6✔
375
        second = 0
6✔
376
        nano = 0
6✔
377

378
        if hour < 0:
6✔
379
            hour = ~hour
×
380
        else:
381
            (minute,), data = read_struct(data, ">b")
6✔
382
            if minute < 0:
6✔
383
                minute = ~minute
×
384
            else:
385
                (second,), data = read_struct(data, ">b")
6✔
386
                if second < 0:
6✔
387
                    second = ~second
×
388
                else:
389
                    (nano,), data = read_struct(data, ">i")
6✔
390

391
        self.hour = hour
6✔
392
        self.minute = minute
6✔
393
        self.second = second
6✔
394
        self.nano = nano
6✔
395
        return data
6✔
396

397
    def do_local_date_time(self, data):
6✔
398
        data = self.do_local_date(data)
6✔
399
        data = self.do_local_time(data)
6✔
400
        return data
6✔
401

402
    def do_zoned_date_time(self, data):
6✔
403
        data = self.do_local_date_time(data)
6✔
404
        data = self.do_zone_offset(data)
6✔
405
        data = self.do_zone_region(data)
6✔
406
        return data
6✔
407

408
    def do_zone_offset(self, data):
6✔
409
        (offset_byte,), data = read_struct(data, ">b")
6✔
410
        if offset_byte == 127:
6✔
411
            (self.offset,), data = read_struct(data, ">i")
×
412
        else:
413
            self.offset = offset_byte * 900
6✔
414
        return data
6✔
415

416
    def do_zone_region(self, data):
6✔
417
        self.zone, data = read_string(data)
6✔
418
        return data
6✔
419

420
    def do_offset_time(self, data):
6✔
421
        data = self.do_local_time(data)
×
422
        data = self.do_zone_offset(data)
×
423
        return data
×
424

425
    def do_offset_date_time(self, data):
6✔
426
        data = self.do_local_date_time(data)
×
427
        data = self.do_zone_offset(data)
×
428
        return data
×
429

430
    def do_year(self, data):
6✔
431
        (self.year,), data = read_struct(data, ">i")
×
432
        return data
×
433

434
    def do_year_month(self, data):
6✔
435
        (self.year, self.month), data = read_struct(data, ">ib")
×
436
        return data
×
437

438
    def do_month_day(self, data):
6✔
439
        (self.month, self.day), data = read_struct(data, ">bb")
×
440
        return data
×
441

442
    def do_period(self, data):
6✔
443
        (self.year, self.month, self.day), data = read_struct(data, ">iii")
×
444
        return data
×
445

446

447
class DefaultObjectTransformer(ObjectTransformer):
6✔
448
    """
449
    Provider of the default object transformers
450
    """
451

452
    KNOWN_TRANSFORMERS = (
6✔
453
        JavaBool,
454
        JavaInt,
455
        JavaList,
456
        JavaMap,
457
        JavaLinkedHashMap,
458
        JavaSet,
459
        JavaTreeSet,
460
        JavaTime,
461
    )
462

463
    def __init__(self):
6✔
464
        # Construct the link: Java class name -> Python transformer
465
        self._type_mapper = {}
6✔
466
        for transformer_class in self.KNOWN_TRANSFORMERS:
6✔
467
            handled_classes = transformer_class.HANDLED_CLASSES
6✔
468
            if isinstance(handled_classes, str):
6✔
469
                # Single class handled
470
                self._type_mapper[handled_classes] = transformer_class
6✔
471
            else:
472
                # Multiple classes handled
473
                for class_name in transformer_class.HANDLED_CLASSES:
6✔
474
                    self._type_mapper[class_name] = transformer_class
6✔
475

476
    def create_instance(self, classdesc):
6✔
477
        # type: (JavaClassDesc) -> Optional[JavaInstance]
478
        """
479
        Transforms a parsed Java object into a Python object
480

481
        :param classdesc: The description of a Java class
482
        :return: The Python form of the object, or the original JavaObject
483
        """
484
        try:
6✔
485
            mapped_type = self._type_mapper[classdesc.name]
6✔
486
        except KeyError:
6✔
487
            # Return None if not handled
488
            return None
6✔
489
        else:
490
            log_debug("---")
6✔
491
            log_debug(classdesc.name)
6✔
492
            log_debug("---")
6✔
493

494
            java_object = mapped_type()
6✔
495
            java_object.classdesc = classdesc
6✔
496

497
            log_debug(">>> java_object: {0}".format(java_object))
6✔
498
            return java_object
6✔
499

500

501
class NumpyArrayTransformer(ObjectTransformer):
6✔
502
    """
503
    Loads arrays as numpy arrays if possible
504
    """
505

506
    # Convertion of a Java type char to its NumPy equivalent
507
    NUMPY_TYPE_MAP = {
6✔
508
        TypeCode.TYPE_BYTE: "B",
509
        TypeCode.TYPE_CHAR: "b",
510
        TypeCode.TYPE_DOUBLE: ">d",
511
        TypeCode.TYPE_FLOAT: ">f",
512
        TypeCode.TYPE_INTEGER: ">i",
513
        TypeCode.TYPE_LONG: ">l",
514
        TypeCode.TYPE_SHORT: ">h",
515
        TypeCode.TYPE_BOOLEAN: ">B",
516
    }
517

518
    def load_array(self, reader, type_code, size):
6✔
519
        # type: (DataStreamReader, TypeCode, int) -> Optional[list]
520
        """
521
        Loads a Java array, if possible
522
        """
523
        if numpy is not None:
×
524
            try:
×
525
                dtype = self.NUMPY_TYPE_MAP[type_code]
×
526
            except KeyError:
×
527
                # Unhandled data type
528
                return None
×
529
            else:
530
                return numpy.fromfile(
×
531
                    reader.file_descriptor, dtype=dtype, count=size,
532
                )
533

534
        return None
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc