• 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.46
/javaobj/v2/beans.py
1
#!/usr/bin/env python3
2
"""
6✔
3
Definition of the beans used to represent the parsed objects
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
from __future__ import absolute_import
6✔
28

29
import logging
6✔
30
from enum import IntEnum
6✔
31
from typing import Any, Dict, List, Optional, Set
6✔
32

33
from ..constants import ClassDescFlags, TypeCode
6✔
34
from ..modifiedutf8 import byte_to_int, decode_modified_utf8
6✔
35
from ..utils import UNICODE_TYPE
6✔
36

37
# ------------------------------------------------------------------------------
38

39
# Module version
40
__version_info__ = (0, 4, 4)
6✔
41
__version__ = ".".join(str(x) for x in __version_info__)
6✔
42

43
# Documentation strings format
44
__docformat__ = "restructuredtext en"
6✔
45

46
# ------------------------------------------------------------------------------
47

48

49
class ContentType(IntEnum):
6✔
50
    """
51
    Types of objects
52
    """
53

54
    INSTANCE = 0
6✔
55
    CLASS = 1
6✔
56
    ARRAY = 2
6✔
57
    STRING = 3
6✔
58
    ENUM = 4
6✔
59
    CLASSDESC = 5
6✔
60
    BLOCKDATA = 6
6✔
61
    EXCEPTIONSTATE = 7
6✔
62

63

64
class ClassDataType(IntEnum):
6✔
65
    """
66
    Class data types
67
    """
68

69
    NOWRCLASS = 0
6✔
70
    WRCLASS = 1
6✔
71
    EXTERNAL_CONTENTS = 2
6✔
72
    OBJECT_ANNOTATION = 3
6✔
73

74

75
class ClassDescType(IntEnum):
6✔
76
    """
77
    Types of class descriptions
78
    """
79

80
    NORMALCLASS = 0
6✔
81
    PROXYCLASS = 1
6✔
82

83

84
class FieldType(IntEnum):
6✔
85
    """
86
    Types of class fields
87
    """
88

89
    BYTE = TypeCode.TYPE_BYTE.value
6✔
90
    CHAR = TypeCode.TYPE_CHAR.value
6✔
91
    DOUBLE = TypeCode.TYPE_DOUBLE.value
6✔
92
    FLOAT = TypeCode.TYPE_FLOAT.value
6✔
93
    INTEGER = TypeCode.TYPE_INTEGER.value
6✔
94
    LONG = TypeCode.TYPE_LONG.value
6✔
95
    SHORT = TypeCode.TYPE_SHORT.value
6✔
96
    BOOLEAN = TypeCode.TYPE_BOOLEAN.value
6✔
97
    ARRAY = TypeCode.TYPE_ARRAY.value
6✔
98
    OBJECT = TypeCode.TYPE_OBJECT.value
6✔
99

100
    def type_code(self):
6✔
101
        # type: () -> TypeCode
102
        """
103
        Converts this FieldType to its matching TypeCode
104
        """
105
        return TypeCode(self.value)
6✔
106

107

108
class ParsedJavaContent(object):  # pylint:disable=R205
6✔
109
    """
110
    Generic representation of data parsed from the stream
111
    """
112

113
    def __init__(self, content_type):
6✔
114
        # type: (ContentType) -> None
115
        self.type = content_type  # type: ContentType
6✔
116
        self.is_exception = False  # type: bool
6✔
117
        self.handle = 0  # type: int
6✔
118

119
    def __str__(self):
6✔
120
        return "[ParseJavaObject 0x{0:x} - {1}]".format(self.handle, self.type)
×
121

122
    __repr__ = __str__
6✔
123

124
    def dump(self, indent=0):
6✔
125
        # type: (int) -> str
126
        """
127
        Base implementation of a parsed object
128
        """
129
        return "\t" * indent + str(self)
×
130

131
    def validate(self):
6✔
132
        """
133
        Validity check on the object
134
        """
135
        pass
6✔
136

137

138
class ExceptionState(ParsedJavaContent):
6✔
139
    """
140
    Representation of a failed parsing
141
    """
142

143
    def __init__(self, exception_object, data):
6✔
144
        # type: (ParsedJavaContent, bytes) -> None
145
        super(ExceptionState, self).__init__(ContentType.EXCEPTIONSTATE)
×
146
        self.exception_object = exception_object
×
147
        self.stream_data = data
×
148
        self.handle = exception_object.handle
×
149

150
    def dump(self, indent=0):
6✔
151
        # type: (int) -> str
152
        """
153
        Returns a dump representation of the exception
154
        """
155
        return "\t" * indent + "[ExceptionState {0:x}]".format(self.handle)
×
156

157

158
class ExceptionRead(Exception):
6✔
159
    """
160
    Exception used to indicate that an exception object has been parsed
161
    """
162

163
    def __init__(self, content):
6✔
164
        # type: (ParsedJavaContent) -> None
165
        self.exception_object = content
×
166

167

168
class JavaString(ParsedJavaContent):
6✔
169
    """
170
    Represents a Java string
171
    """
172

173
    def __init__(self, handle, data):
6✔
174
        # type: (int, bytes) -> None
175
        super(JavaString, self).__init__(ContentType.STRING)
6✔
176
        self.handle = handle
6✔
177
        value, length = decode_modified_utf8(data)
6✔
178
        self.value = value  # type: str
6✔
179
        self.length = length  # type: int
6✔
180

181
    def __repr__(self):
6✔
182
        return repr(self.value)
×
183

184
    def __str__(self):
6✔
185
        return self.value
6✔
186

187
    def dump(self, indent=0):
6✔
188
        # type: (int) -> str
189
        """
190
        Returns a dump representation of the string
191
        """
192
        return "\t" * indent + "[String {0:x}: {1}]".format(
×
193
            self.handle, repr(self.value)
194
        )
195

196
    def __hash__(self):
6✔
197
        return hash(self.value)
6✔
198

199
    def __eq__(self, other):
6✔
200
        return self.value == other
6✔
201

202

203
class JavaField:
6✔
204
    """
205
    Represents a field in a Java class description
206
    """
207

208
    def __init__(self, field_type, name, class_name=None):
6✔
209
        # type: (FieldType, str, Optional[JavaString]) -> None
210
        self.type = field_type
6✔
211
        self.name = name
6✔
212
        self.class_name = class_name
6✔
213
        self.is_inner_class_reference = False
6✔
214

215
        if self.class_name:
6✔
216
            self.validate(self.class_name.value)
6✔
217

218
    def validate(self, java_type):
6✔
219
        # type: (str) -> None
220
        """
221
        Validates the type given as parameter
222
        """
223
        if self.type == FieldType.OBJECT:
6✔
224
            if not java_type:
6✔
225
                raise ValueError("Class name can't be empty")
×
226

227
            if java_type[0] != "L" or java_type[-1] != ";":
6✔
228
                raise ValueError(
×
229
                    "Invalid object field type: {0}".format(java_type)
230
                )
231

232

233
class JavaClassDesc(ParsedJavaContent):
6✔
234
    """
235
    Represents the description of a class
236
    """
237

238
    def __init__(self, class_desc_type):
6✔
239
        # type: (ClassDescType) -> None
240
        super(JavaClassDesc, self).__init__(ContentType.CLASSDESC)
6✔
241

242
        # Type of class description
243
        self.class_type = class_desc_type  # type: ClassDescType
6✔
244

245
        # Class name
246
        self.name = None  # type: Optional[str]
6✔
247

248
        # Serial version UID
249
        self.serial_version_uid = 0  # type: int
6✔
250

251
        # Description flags byte
252
        self.desc_flags = 0  # type: int
6✔
253

254
        # Fields in the class
255
        self.fields = []  # type: List[JavaField]
6✔
256

257
        # Inner classes
258
        self.inner_classes = []  # type: List[JavaClassDesc]
6✔
259

260
        # List of annotations objects
261
        self.annotations = []  # type: List[ParsedJavaContent]
6✔
262

263
        # The super class of this one, if any
264
        self.super_class = None  # type: Optional[JavaClassDesc]
6✔
265

266
        # Indicates if it is a super class
267
        self.is_super_class = False
6✔
268

269
        # List of the interfaces of the class
270
        self.interfaces = []  # type: List[str]
6✔
271

272
        # Set of enum constants
273
        self.enum_constants = set()  # type: Set[str]
6✔
274

275
        # Flag to indicate if this is an inner class
276
        self.is_inner_class = False  # type: bool
6✔
277

278
        # Flag to indicate if this is a local inner class
279
        self.is_local_inner_class = False  # type: bool
6✔
280

281
        # Flag to indicate if this is a static member class
282
        self.is_static_member_class = False  # type: bool
6✔
283

284
    def __str__(self):
6✔
285
        return "[classdesc 0x{0:x}: name {1}, uid {2}]".format(
6✔
286
            self.handle, self.name, self.serial_version_uid
287
        )
288

289
    __repr__ = __str__
6✔
290

291
    def dump(self, indent=0):
6✔
292
        # type: (int) -> str
293
        """
294
        Returns a dump representation of the exception
295
        """
296
        return "\t" * indent + "[classdesc 0x{0:x}: name {1}, uid {2}]".format(
×
297
            self.handle, self.name, self.serial_version_uid
298
        )
299

300
    @property
6✔
301
    def serialVersionUID(self):  # pylint:disable=C0103
5✔
302
        """
303
        Mimics the javaobj API
304
        """
305
        return self.serial_version_uid
6✔
306

307
    @property
6✔
308
    def flags(self):
5✔
309
        """
310
        Mimics the javaobj API
311
        """
312
        return self.desc_flags
6✔
313

314
    @property
6✔
315
    def fields_names(self):
5✔
316
        """
317
        Mimics the javaobj API
318
        """
319
        return [field.name for field in self.fields]
6✔
320

321
    @property
6✔
322
    def fields_types(self):
5✔
323
        """
324
        Mimics the javaobj API
325
        """
326
        return [field.type for field in self.fields]
6✔
327

328
    @property
6✔
329
    def data_type(self):
5✔
330
        """
331
        Computes the data type of this class (Write, No Write, Annotation)
332
        """
333
        if ClassDescFlags.SC_SERIALIZABLE & self.desc_flags:
6✔
334
            return (
6✔
335
                ClassDataType.WRCLASS
336
                if (ClassDescFlags.SC_WRITE_METHOD & self.desc_flags)
337
                else ClassDataType.NOWRCLASS
338
            )
339

340
        if ClassDescFlags.SC_EXTERNALIZABLE & self.desc_flags:
6✔
341
            return (
6✔
342
                ClassDataType.OBJECT_ANNOTATION
343
                if (ClassDescFlags.SC_WRITE_METHOD & self.desc_flags)
344
                else ClassDataType.EXTERNAL_CONTENTS
345
            )
346

347
        raise ValueError("Unhandled Class Data Type")
×
348

349
    def is_array_class(self):
6✔
350
        # type: () -> bool
351
        """
352
        Determines if this is an array type
353
        """
354
        return self.name.startswith("[") if self.name else False
×
355

356
    def get_hierarchy(self, classes):
6✔
357
        # type: (List["JavaClassDesc"]) -> None
358
        """
359
        Generates a list of class descriptions in this class's hierarchy, in
360
        the order described by the Object Stream Serialization Protocol.
361
        This is the order in which fields are read from the stream.
362

363
        :param classes: A list to be filled in with the hierarchy
364
        """
365
        if self.super_class is not None:
6✔
366
            if self.super_class.class_type == ClassDescType.PROXYCLASS:
6✔
367
                logging.warning("Hit a proxy class in super class hierarchy")
×
368
            else:
369
                self.super_class.get_hierarchy(classes)
6✔
370

371
        classes.append(self)
6✔
372

373
    def validate(self):
6✔
374
        """
375
        Checks the validity of this class description
376
        """
377
        serial_or_extern = (
6✔
378
            ClassDescFlags.SC_SERIALIZABLE | ClassDescFlags.SC_EXTERNALIZABLE
379
        )
380
        if (self.desc_flags & serial_or_extern) == 0 and self.fields:
6✔
381
            raise ValueError(
×
382
                "Non-serializable, non-externalizable class has fields"
383
            )
384

385
        if self.desc_flags & serial_or_extern == serial_or_extern:
6✔
386
            raise ValueError("Class is both serializable and externalizable")
×
387

388
        if self.desc_flags & ClassDescFlags.SC_ENUM:
6✔
389
            if self.fields or self.interfaces:
6✔
390
                raise ValueError(
×
391
                    "Enums shouldn't implement interfaces "
392
                    "or have non-constant fields"
393
                )
394
        else:
395
            if self.enum_constants:
6✔
396
                raise ValueError(
×
397
                    "Non-enum classes shouldn't have enum constants"
398
                )
399

400

401
class JavaInstance(ParsedJavaContent):
6✔
402
    """
403
    Represents an instance of Java object
404
    """
405

406
    def __init__(self):
6✔
407
        super(JavaInstance, self).__init__(ContentType.INSTANCE)
6✔
408
        self.classdesc = None  # type: JavaClassDesc
6✔
409
        self.field_data = {}  # type: Dict[JavaClassDesc, Dict[JavaField, Any]]
6✔
410
        self.annotations = (
6✔
411
            {}
412
        )  # type: Dict[JavaClassDesc, List[ParsedJavaContent]]
413
        self.is_external_instance = False
6✔
414

415
    def __str__(self):
6✔
416
        return "[instance 0x{0:x}: type {1}]".format(
6✔
417
            self.handle, self.classdesc.name
418
        )
419

420
    __repr__ = __str__
6✔
421

422
    def dump(self, indent=0):
6✔
423
        # type: (int) -> str
424
        """
425
        Returns a dump representation of the exception
426
        """
427
        prefix = "\t" * indent
×
428
        sub_prefix = "\t" * (indent + 1)
×
429

430
        dump = [
×
431
            prefix
432
            + "[instance 0x{0:x}: {1:x} / {2}]".format(
433
                self.handle, self.classdesc.handle, self.classdesc.name
434
            )
435
        ]
436

437
        for cd, annotations in self.annotations.items():
×
438
            dump.append(
×
439
                "{0}{1} -- {2} annotations".format(
440
                    prefix, cd.name, len(annotations)
441
                )
442
            )
443
            for ann in annotations:
×
444
                dump.append(sub_prefix + repr(ann))
×
445

446
        for cd, fields in self.field_data.items():
×
447
            dump.append(
×
448
                "{0}{1} -- {2} fields".format(prefix, cd.name, len(fields))
449
            )
450
            for field, value in fields.items():
×
451
                if isinstance(value, ParsedJavaContent):
×
452
                    if self.handle != 0 and value.handle == self.handle:
×
453
                        value_str = "this"
×
454
                    else:
455
                        value_str = "\n" + value.dump(indent + 2)
×
456
                else:
457
                    value_str = repr(value)
×
458

459
                dump.append(
×
460
                    "{0}{1} {2}: {3}".format(
461
                        sub_prefix, field.type.name, field.name, value_str
462
                    )
463
                )
464

465
        dump.append(prefix + "[/instance 0x{0:x}]".format(self.handle))
×
466
        return "\n".join(dump)
×
467

468
    def __getattr__(self, name):
6✔
469
        """
470
        Returns the field with the given name
471
        """
472
        for cd_fields in self.field_data.values():
6✔
473
            for field, value in cd_fields.items():
6✔
474
                if field.name == name:
6✔
475
                    return value
6✔
476

477
        raise AttributeError(name)
×
478

479
    def get_class(self):
6✔
480
        """
481
        Returns the class of this instance
482
        """
483
        return self.classdesc
6✔
484

485
    def load_from_blockdata(
6✔
486
        self, parser, reader, indent=0
487
    ):  # pylint:disable=W0613,R0201
488
        """
489
        Reads content stored in a block data.
490

491
        This method is called only if the class description has both the
492
        ``SC_EXTERNALIZABLE`` and ``SC_BLOCK_DATA`` flags set.
493

494
        The stream parsing will stop and fail if this method returns False.
495

496
        :param parser: The JavaStreamParser in use
497
        :param reader: The underlying data stream reader
498
        :param indent: Indentation to use in logs
499
        :return: True on success, False on error
500
        """
501
        return False
×
502

503
    def load_from_instance(self, indent=0):  # pylint:disable=W0613,R0201
6✔
504
        # type: (int) -> bool
505
        """
506
        Updates the content of this instance from its parsed fields and
507
        annotations
508

509
        :param indent: Indentation to use in logs
510
        :return: True on success, False on error (currently ignored)
511
        """
512
        return False
6✔
513

514

515
class JavaClass(ParsedJavaContent):
6✔
516
    """
517
    Represents a stored Java class
518
    """
519

520
    def __init__(self, handle, class_desc):
6✔
521
        # type: (int, JavaClassDesc) -> None
522
        super(JavaClass, self).__init__(ContentType.CLASS)
6✔
523
        self.handle = handle
6✔
524
        self.classdesc = class_desc
6✔
525

526
    def __str__(self):
6✔
527
        return "[class 0x{0:x}: {1}]".format(self.handle, self.classdesc)
×
528

529
    __repr__ = __str__
6✔
530

531
    @property
6✔
532
    def name(self):
5✔
533
        """
534
        Mimics the javaobj API
535
        """
536
        return self.classdesc.name
6✔
537

538

539
class JavaEnum(ParsedJavaContent):
6✔
540
    """
541
    Represents an enumeration value
542
    """
543

544
    def __init__(self, handle, class_desc, value):
6✔
545
        # type: (int, JavaClassDesc, JavaString) -> None
546
        super(JavaEnum, self).__init__(ContentType.ENUM)
6✔
547
        self.handle = handle
6✔
548
        self.classdesc = class_desc
6✔
549
        self.value = value
6✔
550

551
    def __str__(self):
6✔
552
        return "[Enum 0x{0:x}: {1}]".format(self.handle, self.value)
6✔
553

554
    __repr__ = __str__
6✔
555

556
    @property
6✔
557
    def constant(self):
5✔
558
        """
559
        Mimics the javaobj API
560
        """
561
        return self.value
6✔
562

563

564
class JavaArray(ParsedJavaContent, list):
6✔
565
    """
566
    Represents a Java array
567
    """
568

569
    def __init__(self, handle, class_desc, field_type, content):
6✔
570
        # type: (int, JavaClassDesc, FieldType, List[Any]) -> None
571
        list.__init__(self, content)
6✔
572
        ParsedJavaContent.__init__(self, ContentType.ARRAY)
6✔
573
        self.handle = handle
6✔
574
        self.classdesc = class_desc
6✔
575
        self.field_type = field_type
6✔
576
        self.data = content
6✔
577

578
    def __str__(self):
6✔
579
        return "[{0}]".format(", ".join(repr(x) for x in self))
×
580

581
    __repr__ = __str__
6✔
582

583
    def dump(self, indent=0):
6✔
584
        # type: (int) -> str
585
        """
586
        Returns a dump representation of the array
587
        """
588
        prefix = "\t" * indent
×
589
        sub_prefix = "\t" * (indent + 1)
×
590
        dump = [
×
591
            "{0}[array 0x{1:x}: {2} items - stored as {3}]".format(
592
                prefix, self.handle, len(self), type(self.data).__name__
593
            )
594
        ]
595
        for x in self:
×
596
            if isinstance(x, ParsedJavaContent):
×
597
                if self.handle != 0 and x.handle == self.handle:
×
598
                    dump.append("this,")
×
599
                else:
600
                    dump.append(x.dump(indent + 1) + ",")
×
601
            else:
602
                dump.append(sub_prefix + repr(x) + ",")
×
603
        dump.append(prefix + "[/array 0x{0:x}]".format(self.handle))
×
604
        return "\n".join(dump)
×
605

606
    @property
6✔
607
    def _data(self):
5✔
608
        """
609
        Mimics the javaobj API
610
        """
611
        return tuple(self)
6✔
612

613

614
class BlockData(ParsedJavaContent):
6✔
615
    """
616
    Represents a data block
617
    """
618

619
    def __init__(self, data):
6✔
620
        # type: (bytes) -> None
621
        super(BlockData, self).__init__(ContentType.BLOCKDATA)
6✔
622
        self.data = data
6✔
623

624
    def __str__(self):
6✔
625
        return "[blockdata 0x{0:x}: {1} bytes]".format(
×
626
            self.handle, len(self.data)
627
        )
628

629
    def __repr__(self):
6✔
630
        return repr(self.data)
×
631

632
    def __eq__(self, other):
6✔
633
        if isinstance(other, (str, UNICODE_TYPE)):
6✔
634
            other_data = tuple(ord(x) for x in other)
6✔
635
        elif isinstance(other, bytes):
6✔
636
            other_data = tuple(byte_to_int(x) for x in other)
6✔
637
        else:
638
            # Can't compare
639
            return False
×
640

641
        return other_data == tuple(byte_to_int(x) for x in self.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