• 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

69.36
/javaobj/v2/core.py
1
#!/usr/bin/env python3
2
"""
6✔
3
Second parsing approach for javaobj, using the same approach as jdeserialize
4
See: https://github.com/frohoff/jdeserialize
5

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

11
..
12

13
    Copyright 2024 Thomas Calmant
14

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

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

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

28
from __future__ import absolute_import
6✔
29

30
import logging
6✔
31
import os
6✔
32
from typing import (  # pylint:disable=W0611
6✔
33
    IO,
34
    Any,
35
    Callable,
36
    Dict,
37
    List,
38
    Optional,
39
)
40

41
from ..constants import (
6✔
42
    PRIMITIVE_TYPES,
43
    StreamConstants,
44
    TerminalCode,
45
    TypeCode,
46
)
47
from ..modifiedutf8 import (  # pylint:disable=W0611  # noqa: F401
6✔
48
    decode_modified_utf8,
49
)
50
from . import api  # pylint:disable=W0611
6✔
51
from .beans import (
6✔
52
    BlockData,
53
    ClassDataType,
54
    ClassDescType,
55
    ExceptionRead,
56
    ExceptionState,
57
    FieldType,
58
    JavaArray,
59
    JavaClass,
60
    JavaClassDesc,
61
    JavaEnum,
62
    JavaField,
63
    JavaInstance,
64
    JavaString,
65
    ParsedJavaContent,
66
)
67
from .stream import DataStreamReader
6✔
68
from .transformers import DefaultObjectTransformer
6✔
69

70
# ------------------------------------------------------------------------------
71

72
# Module version
73
__version_info__ = (0, 4, 4)
6✔
74
__version__ = ".".join(str(x) for x in __version_info__)
6✔
75

76
# Documentation strings format
77
__docformat__ = "restructuredtext en"
6✔
78

79
# ------------------------------------------------------------------------------
80

81

82
class JavaStreamParser(api.IJavaStreamParser):
6✔
83
    """
84
    Parses a Java stream
85
    """
86

87
    def __init__(self, fd, transformers):
6✔
88
        # type: (IO[bytes], List[api.ObjectTransformer]) -> None
89
        """
90
        :param fd: File-object to read from
91
        :param transformers: Custom object transformers
92
        """
93
        # Input stream
94
        self.__fd = fd
6✔
95
        self.__reader = DataStreamReader(fd)
6✔
96

97
        # Object transformers
98
        self.__transformers = list(transformers)
6✔
99

100
        # Logger
101
        self._log = logging.getLogger("javaobj.parser")
6✔
102

103
        # Handles
104
        self.__handle_maps = []  # type: List[Dict[int, ParsedJavaContent]]
6✔
105
        self.__handles = {}  # type: Dict[int, ParsedJavaContent]
6✔
106

107
        # Initial handle value
108
        self.__current_handle = StreamConstants.BASE_REFERENCE_IDX.value
6✔
109

110
        # Definition of the type code handlers
111
        # Each takes the type code as argument
112
        self.__type_code_handlers = {
6✔
113
            TerminalCode.TC_OBJECT: self._do_object,
114
            TerminalCode.TC_CLASS: self._do_class,
115
            TerminalCode.TC_ARRAY: self._do_array,
116
            TerminalCode.TC_STRING: self._read_new_string,
117
            TerminalCode.TC_LONGSTRING: self._read_new_string,
118
            TerminalCode.TC_ENUM: self._do_enum,
119
            TerminalCode.TC_CLASSDESC: self._do_classdesc,
120
            TerminalCode.TC_PROXYCLASSDESC: self._do_classdesc,
121
            TerminalCode.TC_REFERENCE: self._do_reference,
122
            TerminalCode.TC_NULL: self._do_null,
123
            TerminalCode.TC_EXCEPTION: self._do_exception,
124
            TerminalCode.TC_BLOCKDATA: self._do_block_data,
125
            TerminalCode.TC_BLOCKDATALONG: self._do_block_data,
126
        }  # type: Dict[int, Callable[[int], ParsedJavaContent]]
127

128
    def run(self):
6✔
129
        # type: () -> List[ParsedJavaContent]
130
        """
131
        Parses the input stream
132
        """
133
        # Check the magic byte
134
        magic = self.__reader.read_ushort()
6✔
135
        if magic != StreamConstants.STREAM_MAGIC:
6✔
136
            raise ValueError("Invalid file magic: 0x{0:x}".format(magic))
×
137

138
        # Check the stream version
139
        version = self.__reader.read_ushort()
6✔
140
        if version != StreamConstants.STREAM_VERSION:
6✔
141
            raise ValueError("Invalid file version: 0x{0:x}".format(version))
×
142

143
        # Reset internal state
144
        self._reset()
6✔
145

146
        # Read content
147
        contents = []  # type: List[ParsedJavaContent]
6✔
148
        while True:
4✔
149
            self._log.info("Reading next content")
6✔
150
            start = self.__fd.tell()
6✔
151
            try:
6✔
152
                type_code = self.__reader.read_byte()
6✔
153
            except EOFError:
6✔
154
                # End of file
155
                break
6✔
156

157
            if type_code == TerminalCode.TC_RESET:
6✔
158
                # Explicit reset
159
                self._reset()
×
160
                continue
×
161

162
            parsed_content = self._read_content(type_code, True)
6✔
163
            self._log.debug("Read: %s", parsed_content)
6✔
164
            if parsed_content is not None and parsed_content.is_exception:
6✔
165
                # Get the raw data between the start of the object and our
166
                # current position
167
                end = self.__fd.tell()
×
168
                self.__fd.seek(start, os.SEEK_SET)
×
169
                stream_data = self.__fd.read(end - start)
×
170

171
                # Prepare an exception object
172
                parsed_content = ExceptionState(parsed_content, stream_data)
×
173

174
            contents.append(parsed_content)
6✔
175

176
        for content in self.__handles.values():
6✔
177
            content.validate()
6✔
178

179
        # TODO: connect member classes ? (see jdeserialize @ 864)
180

181
        if self.__handles:
6✔
182
            self.__handle_maps.append(self.__handles.copy())
6✔
183

184
        return contents
6✔
185

186
    def dump(self, content):
6✔
187
        # type: (List[ParsedJavaContent]) -> str
188
        """
189
        Dumps to a string the given objects
190
        """
191
        lines = []  # type: List[str]
×
192

193
        # Stream content
194
        lines.append("//// BEGIN stream content output")
×
195
        lines.extend(str(c) for c in content)
×
196
        lines.append("//// END stream content output")
×
197
        lines.append("")
×
198

199
        lines.append("//// BEGIN instance dump")
×
200
        for c in self.__handles.values():
×
201
            if isinstance(c, JavaInstance):
×
202
                instance = c  # type: JavaInstance
×
203
                lines.extend(self._dump_instance(instance))
×
204
        lines.append("//// END instance dump")
×
205
        lines.append("")
×
206
        return "\n".join(lines)
×
207

208
    @staticmethod
6✔
209
    def _dump_instance(instance):
5✔
210
        # type: (JavaInstance) -> List[str]
211
        """
212
        Dumps an instance to a set of lines
213
        """
214
        lines = []  # type: List[str]
×
215
        lines.append(
×
216
            "[instance 0x{0:x}: 0x{1:x} / {2}".format(
217
                instance.handle,
218
                instance.classdesc.handle,
219
                instance.classdesc.name,
220
            )
221
        )
222

223
        if instance.annotations:
×
224
            lines.append("\tobject annotations:")
×
225
            for cd, annotation in instance.annotations.items():
×
226
                lines.append("\t" + (cd.name or "null"))
×
227
                for c in annotation:
×
228
                    lines.append("\t\t" + str(c))
×
229

230
        if instance.field_data:
×
231
            lines.append("\tfield data:")
×
232
            for field, obj in instance.field_data.items():
×
233
                line = "\t\t" + (field.name or "null") + ": "
×
234
                if isinstance(obj, ParsedJavaContent):
×
235
                    content = obj  # type: ParsedJavaContent
×
236
                    h = content.handle
×
237
                    if h == instance.handle:
×
238
                        line += "this"
×
239
                    else:
240
                        line += "r0x{0:x}".format(h)
×
241

242
                    line += ": " + str(content)
×
243
                else:
244
                    line += str(obj)
×
245

246
                lines.append(line)
×
247

248
        lines.append("]")
×
249
        return lines
×
250

251
    def _reset(self):
6✔
252
        """
253
        Resets the internal state of the parser
254
        """
255
        if self.__handles:
6✔
256
            self.__handle_maps.append(self.__handles.copy())
×
257

258
        self.__handles.clear()
6✔
259

260
        # Reset handle index
261
        self.__current_handle = StreamConstants.BASE_REFERENCE_IDX
6✔
262

263
    def _new_handle(self):
6✔
264
        # type: () -> int
265
        """
266
        Returns a new handle value
267
        """
268
        handle = self.__current_handle
6✔
269
        self.__current_handle += 1
6✔
270
        return handle
6✔
271

272
    def _set_handle(self, handle, content):
6✔
273
        # type: (int, ParsedJavaContent) -> None
274
        """
275
        Stores the reference to an object
276
        """
277
        if handle in self.__handles:
6✔
278
            raise ValueError("Trying to reset handle {0:x}".format(handle))
×
279

280
        self.__handles[handle] = content
6✔
281

282
    @staticmethod
6✔
283
    def _do_null(_):
5✔
284
        """
285
        The easiest one
286
        """
287
        return None
6✔
288

289
    def _read_content(self, type_code, block_data, class_desc=None):
6✔
290
        # type: (int, bool, Optional[JavaClassDesc]) -> ParsedJavaContent
291
        """
292
        Parses the next content
293
        """
294
        if not block_data and type_code in (
6✔
295
            TerminalCode.TC_BLOCKDATA,
296
            TerminalCode.TC_BLOCKDATALONG,
297
        ):
298
            raise ValueError("Got a block data, but not allowed here.")
×
299

300
        try:
6✔
301
            # Look for a handler for that type code
302
            handler = self.__type_code_handlers[type_code]
6✔
303
        except KeyError:
6✔
304
            # Look for an external reader
305
            if (
6✔
306
                class_desc
307
                and class_desc.name
308
                and class_desc.data_type == ClassDataType.WRCLASS
309
            ):
310
                # Return its result immediately
311
                return self._custom_readObject(class_desc.name)
6✔
312

313
            # No valid custom reader: abandon
314
            raise ValueError("Unknown type code: 0x{0:x}".format(type_code))
×
315
        else:
316
            try:
6✔
317
                # Parse the object
318
                return handler(type_code)
6✔
319
            except ExceptionRead as ex:
×
320
                # We found an exception object: return it (raise later)
321
                return ex.exception_object
×
322

323
    def _read_new_string(self, type_code):
6✔
324
        # type: (int) -> JavaString
325
        """
326
        Reads a Java String
327
        """
328
        if type_code == TerminalCode.TC_REFERENCE:
6✔
329
            # Got a reference
330
            previous = self._do_reference()
6✔
331
            if not isinstance(previous, JavaString):
6✔
332
                raise ValueError("Invalid reference to a Java string")
×
333
            return previous
6✔
334

335
        # Assign a new handle
336
        handle = self._new_handle()
6✔
337

338
        # Read the length
339
        if type_code == TerminalCode.TC_STRING:
6✔
340
            length = self.__reader.read_ushort()
6✔
341
        elif type_code == TerminalCode.TC_LONGSTRING:
×
342
            length = self.__reader.read_long()
×
343
            if length < 0 or length > 2147483647:
×
344
                raise ValueError("Invalid string length: {0}".format(length))
×
345

346
            if length < 65536:
×
347
                self._log.warning("Small string stored as a long one")
×
348

349
        # Parse the content
350
        data = self.__fd.read(length)
6✔
351
        java_str = JavaString(handle, data)
6✔
352

353
        # Store the reference to the string
354
        self._set_handle(handle, java_str)
6✔
355
        return java_str
6✔
356

357
    def _read_classdesc(self):
6✔
358
        # type: () -> JavaClassDesc
359
        """
360
        Reads a class description with its type code
361
        """
362
        type_code = self.__reader.read_byte()
6✔
363
        return self._do_classdesc(type_code)
6✔
364

365
    def _do_classdesc(self, type_code):
6✔
366
        # type: (int) -> JavaClassDesc
367
        """
368
        Parses a class description
369
        """
370
        if type_code == TerminalCode.TC_CLASSDESC:
6✔
371
            # Do the real job
372
            name = self.__reader.read_UTF()
6✔
373
            serial_version_uid = self.__reader.read_long()
6✔
374
            handle = self._new_handle()
6✔
375
            desc_flags = self.__reader.read_byte()
6✔
376
            nb_fields = self.__reader.read_short()
6✔
377

378
            if nb_fields < 0:
6✔
379
                raise ValueError("Invalid field count: {0}".format(nb_fields))
×
380

381
            fields = []  # type: List[JavaField]
6✔
382
            for _ in range(nb_fields):
6✔
383
                field_type = self.__reader.read_byte()
6✔
384
                field_name = self.__reader.read_UTF()
6✔
385
                class_name = None
6✔
386

387
                if field_type in (TypeCode.TYPE_OBJECT, TypeCode.TYPE_ARRAY):
6✔
388
                    # String type code
389
                    str_type_code = self.__reader.read_byte()
6✔
390
                    class_name = self._read_new_string(str_type_code)
6✔
391
                elif field_type not in PRIMITIVE_TYPES:
6✔
392
                    raise ValueError(
×
393
                        "Invalid field type char: 0x{0:x}".format(field_type)
394
                    )
395

396
                fields.append(
6✔
397
                    JavaField(FieldType(field_type), field_name, class_name)
398
                )
399

400
            # Setup the class description bean
401
            class_desc = JavaClassDesc(ClassDescType.NORMALCLASS)
6✔
402
            class_desc.name = name
6✔
403
            class_desc.serial_version_uid = serial_version_uid
6✔
404
            class_desc.handle = handle
6✔
405
            class_desc.desc_flags = desc_flags
6✔
406
            class_desc.fields = fields
6✔
407
            class_desc.annotations = self._read_class_annotations(class_desc)
6✔
408
            class_desc.super_class = self._read_classdesc()
6✔
409

410
            if class_desc.super_class:
6✔
411
                class_desc.super_class.is_super_class = True
6✔
412

413
            # Store the reference to the parsed bean
414
            self._set_handle(handle, class_desc)
6✔
415
            return class_desc
6✔
416
        elif type_code == TerminalCode.TC_NULL:
6✔
417
            # Null reference
418
            return None
6✔
419
        elif type_code == TerminalCode.TC_REFERENCE:
6✔
420
            # Reference to an already loading class description
421
            previous = self._do_reference()
6✔
422
            if not isinstance(previous, JavaClassDesc):
6✔
423
                raise ValueError(
×
424
                    "Referenced object is not a class description"
425
                )
426
            return previous
6✔
427
        elif type_code == TerminalCode.TC_PROXYCLASSDESC:
×
428
            # Proxy class description
429
            handle = self._new_handle()
×
430
            nb_interfaces = self.__reader.read_int()
×
431
            interfaces = [
×
432
                self.__reader.read_UTF() for _ in range(nb_interfaces)
433
            ]
434

435
            class_desc = JavaClassDesc(ClassDescType.PROXYCLASS)
×
436
            class_desc.handle = handle
×
437
            class_desc.interfaces = interfaces
×
438
            class_desc.annotations = self._read_class_annotations()
×
439
            class_desc.super_class = self._read_classdesc()
×
440

441
            if class_desc.super_class:
×
442
                class_desc.super_class.is_super_class = True
×
443

444
            # Store the reference to the parsed bean
445
            self._set_handle(handle, class_desc)
×
446
            return class_desc
×
447

448
        raise ValueError("Expected a valid class description starter")
×
449

450
    def _custom_readObject(self, class_name):
6✔
451
        # type: (str) -> ParsedJavaContent
452
        """
453
        Reads an object with a custom serialization process
454

455
        :param class_name: Name of the class to load
456
        :return: The parsed object
457
        :raise ValueError: Unknown kind of class
458
        """
459
        self.__fd.seek(-1, os.SEEK_CUR)
6✔
460
        for transformer in self.__transformers:
6✔
461
            class_data = transformer.load_custom_writeObject(
6✔
462
                self, self.__reader, class_name
463
            )
464
            if class_data:
6✔
465
                return class_data
6✔
466

467
        raise ValueError("Custom readObject can not be processed")
×
468

469
    def _read_class_annotations(self, class_desc=None):
6✔
470
        # type: (Optional[JavaClassDesc]) -> List[ParsedJavaContent]
471
        """
472
        Reads the annotations associated to a class
473
        """
474
        contents = []  # type: List[ParsedJavaContent]
6✔
475
        while True:
4✔
476
            type_code = self.__reader.read_byte()
6✔
477
            if type_code == TerminalCode.TC_ENDBLOCKDATA:
6✔
478
                # We're done here
479
                return contents
6✔
480
            elif type_code == TerminalCode.TC_RESET:
6✔
481
                # Reset references
482
                self._reset()
×
483
                continue
×
484

485
            java_object = self._read_content(type_code, True, class_desc)
6✔
486

487
            if java_object is not None and java_object.is_exception:
6✔
488
                # Found an exception: raise it
489
                raise ExceptionRead(java_object)
×
490

491
            contents.append(java_object)
6✔
492

493
        raise Exception("Class annotation reading stopped before end")
494

495
    def _create_instance(self, class_desc):
6✔
496
        # type: (JavaClassDesc) -> JavaInstance
497
        """
498
        Creates a JavaInstance object, by a transformer if possible
499
        """
500
        # Try to create the transformed object
501
        for transformer in self.__transformers:
6✔
502
            instance = transformer.create_instance(class_desc)
6✔
503
            if instance is not None:
6✔
504
                if class_desc.name:
6✔
505
                    instance.is_external_instance = not self._is_default_supported(
6✔
506
                        class_desc.name
507
                    )
508
                return instance
6✔
509

510
        return JavaInstance()
6✔
511

512
    def _do_object(self, type_code=0):
6✔
513
        # type: (int) -> JavaInstance
514
        """
515
        Parses an object
516
        """
517
        # Parse the object class description
518
        class_desc = self._read_classdesc()
6✔
519

520
        # Assign a new handle
521
        handle = self._new_handle()
6✔
522
        self._log.debug(
6✔
523
            "Reading new object: handle %x, classdesc %s", handle, class_desc
524
        )
525

526
        # Prepare the instance object
527
        instance = self._create_instance(class_desc)
6✔
528
        instance.classdesc = class_desc
6✔
529
        instance.handle = handle
6✔
530

531
        # Store the instance
532
        self._set_handle(handle, instance)
6✔
533

534
        # Read the instance content
535
        self._read_class_data(instance)
6✔
536
        self._log.debug("Done reading object handle %x", handle)
6✔
537
        return instance
6✔
538

539
    def _is_default_supported(self, class_name):
6✔
540
        # type: (str) -> bool
541
        """
542
        Checks if this class is supported by the default object transformer
543
        """
544
        default_transf = [
6✔
545
            x
546
            for x in self.__transformers
547
            if isinstance(x, DefaultObjectTransformer)
548
        ]
549
        return (
6✔
550
            bool(default_transf)
551
            and class_name in default_transf[0]._type_mapper
552
        )
553

554
    def _read_class_data(self, instance):
6✔
555
        # type: (JavaInstance) -> None
556
        """
557
        Reads the content of an instance
558
        """
559
        # Read the class hierarchy
560
        classes = []  # type: List[JavaClassDesc]
6✔
561
        instance.classdesc.get_hierarchy(classes)
6✔
562

563
        all_data = {}  # type: Dict[JavaClassDesc, Dict[JavaField, Any]]
6✔
564
        annotations = {}  # type: Dict[JavaClassDesc, List[ParsedJavaContent]]
6✔
565

566
        for cd in classes:
6✔
567
            values = {}  # type: Dict[JavaField, Any]
6✔
568
            cd.validate()
6✔
569
            if (
6✔
570
                cd.data_type == ClassDataType.NOWRCLASS
571
                or cd.data_type == ClassDataType.WRCLASS
572
            ):
573
                if (
6✔
574
                    cd.data_type == ClassDataType.WRCLASS
575
                    and instance.is_external_instance
576
                ):
577
                    annotations[cd] = self._read_class_annotations(cd)
6✔
578
                else:
579
                    for field in cd.fields:
6✔
580
                        values[field] = self._read_field_value(field.type)
6✔
581
                    all_data[cd] = values
6✔
582

583
                    if cd.data_type == ClassDataType.WRCLASS:
6✔
584
                        annotations[cd] = self._read_class_annotations(cd)
6✔
585
            else:
586
                if cd.data_type == ClassDataType.OBJECT_ANNOTATION:
6✔
587
                    # Call the transformer if possible
588
                    if not instance.load_from_blockdata(self, self.__reader):
×
589
                        # Can't read :/
590
                        raise ValueError(
×
591
                            "hit externalizable with nonzero SC_BLOCK_DATA; "
592
                            "can't interpret data"
593
                        )
594
                annotations[cd] = self._read_class_annotations(cd)
6✔
595

596
        # Fill the instance object
597
        instance.annotations = annotations
6✔
598
        instance.field_data = all_data
6✔
599

600
        # Load transformation from the fields and annotations
601
        instance.load_from_instance()
6✔
602

603
    def _read_field_value(self, field_type):
6✔
604
        # type: (FieldType) -> Any
605
        """
606
        Reads the value of an instance field
607
        """
608
        if field_type == FieldType.BYTE:
6✔
609
            return self.__reader.read_byte()
6✔
610
        if field_type == FieldType.CHAR:
6✔
611
            return self.__reader.read_char()
6✔
612
        if field_type == FieldType.DOUBLE:
6✔
613
            return self.__reader.read_double()
6✔
614
        if field_type == FieldType.FLOAT:
6✔
615
            return self.__reader.read_float()
6✔
616
        if field_type == FieldType.INTEGER:
6✔
617
            return self.__reader.read_int()
6✔
618
        if field_type == FieldType.LONG:
6✔
619
            return self.__reader.read_long()
6✔
620
        if field_type == FieldType.SHORT:
6✔
621
            return self.__reader.read_short()
×
622
        if field_type == FieldType.BOOLEAN:
6✔
623
            return self.__reader.read_bool()
6✔
624
        if field_type in (FieldType.OBJECT, FieldType.ARRAY):
6✔
625
            sub_type_code = self.__reader.read_byte()
6✔
626
            if field_type == FieldType.ARRAY:
6✔
627
                if sub_type_code == TerminalCode.TC_NULL:
6✔
628
                    # Seems required, according to issue #46
629
                    return None
×
630
                if sub_type_code == TerminalCode.TC_REFERENCE:
6✔
631
                    return self._do_classdesc(sub_type_code)
×
632
                if sub_type_code != TerminalCode.TC_ARRAY:
6✔
633
                    raise ValueError(
×
634
                        "Array type listed, but type code != TC_ARRAY"
635
                    )
636

637
            content = self._read_content(sub_type_code, False)
6✔
638
            if content is not None and content.is_exception:
6✔
639
                raise ExceptionRead(content)
×
640

641
            return content
6✔
642

643
        raise ValueError("Can't process type: {0}".format(field_type))
×
644

645
    def _do_reference(self, type_code=0):
6✔
646
        # type: (int) -> ParsedJavaContent
647
        """
648
        Returns an object already parsed
649
        """
650
        handle = self.__reader.read_int()
6✔
651
        try:
6✔
652
            return self.__handles[handle]
6✔
653
        except KeyError:
×
654
            raise ValueError("Invalid reference handle: {0:x}".format(handle))
×
655

656
    def _do_enum(self, type_code):
6✔
657
        # type: (int) -> JavaEnum
658
        """
659
        Parses an enumeration
660
        """
661
        cd = self._read_classdesc()
6✔
662
        if cd is None:
6✔
663
            raise ValueError("Enum description can't be null")
×
664

665
        handle = self._new_handle()
6✔
666

667
        # Read the enum string
668
        sub_type_code = self.__reader.read_byte()
6✔
669
        enum_str = self._read_new_string(sub_type_code)
6✔
670
        cd.enum_constants.add(enum_str.value)
6✔
671

672
        # Store the object
673
        enum_obj = JavaEnum(handle, cd, enum_str)
6✔
674
        self._set_handle(handle, enum_obj)
6✔
675
        return enum_obj
6✔
676

677
    def _do_class(self, type_code):
6✔
678
        # type: (int) -> JavaClass
679
        """
680
        Parses a class
681
        """
682
        cd = self._read_classdesc()
6✔
683
        handle = self._new_handle()
6✔
684
        class_obj = JavaClass(handle, cd)
6✔
685

686
        # Store the class object
687
        self._set_handle(handle, class_obj)
6✔
688
        return class_obj
6✔
689

690
    def _do_array(self, type_code):
6✔
691
        # type: (int) -> JavaArray
692
        """
693
        Parses an array
694
        """
695
        cd = self._read_classdesc()
6✔
696
        handle = self._new_handle()
6✔
697
        if not cd.name or len(cd.name) < 2:
6✔
698
            raise ValueError("Invalid name in array class description")
×
699

700
        # ParsedJavaContent type
701
        content_type_byte = ord(cd.name[1].encode("latin1"))
6✔
702
        field_type = FieldType(content_type_byte)
6✔
703

704
        # Array size
705
        size = self.__reader.read_int()
6✔
706
        if size < 0:
6✔
707
            raise ValueError("Invalid array size")
×
708

709
        # Array content
710
        for transformer in self.__transformers:
6✔
711
            content = transformer.load_array(
6✔
712
                self.__reader, field_type.type_code(), size
713
            )
714
            if content is not None:
6✔
715
                break
×
716
        else:
717
            content = [self._read_field_value(field_type) for _ in range(size)]
6✔
718

719
        return JavaArray(handle, cd, field_type, content)
6✔
720

721
    def _do_exception(self, type_code):
6✔
722
        # type: (int) -> ParsedJavaContent
723
        """
724
        Read the content of a thrown exception
725
        """
726
        # Start by resetting current state
727
        self._reset()
×
728

729
        type_code = self.__reader.read_byte()
×
730
        if type_code == TerminalCode.TC_RESET:
×
731
            raise ValueError("TC_RESET read while reading exception")
×
732

733
        content = self._read_content(type_code, False)
×
734
        if content is None:
×
735
            raise ValueError("Null exception object")
×
736

737
        if not isinstance(content, JavaInstance):
×
738
            raise ValueError("Exception object is not an instance")
×
739

740
        if content.is_exception:
×
741
            raise ExceptionRead(content)
×
742

743
        # Strange object ?
744
        content.is_exception = True
×
745
        self._reset()
×
746
        return content
×
747

748
    def _do_block_data(self, type_code):
6✔
749
        # type: (int) -> BlockData
750
        """
751
        Reads a block data
752
        """
753
        # Parse the size
754
        if type_code == TerminalCode.TC_BLOCKDATA:
6✔
755
            size = self.__reader.read_ubyte()
6✔
756
        elif type_code == TerminalCode.TC_BLOCKDATALONG:
×
757
            size = self.__reader.read_int()
×
758
        else:
759
            raise ValueError("Invalid type code for blockdata")
×
760

761
        if size < 0:
6✔
762
            raise ValueError("Invalid value for block data size")
×
763

764
        # Read the block
765
        data = self.__fd.read(size)
6✔
766
        return BlockData(data)
6✔
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