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

tcalmant / python-javaobj / 26726380856

31 May 2026 10:31PM UTC coverage: 78.709% (+0.008%) from 78.701%
26726380856

Pull #63

github

web-flow
Merge 01d037f2a into 519fc2167
Pull Request #63: Addition of a v3 package

808 of 1023 new or added lines in 7 files covered. (78.98%)

2403 of 3053 relevant lines covered (78.71%)

4.44 hits per line

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

78.65
/javaobj/v3/_compat.py
1
#!/usr/bin/env python3
2
"""
3
Migration helpers from javaobj v1 / v2 to v3
4

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

10
..
11

12
    Copyright 2026 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
from typing import Any
3✔
29

30
# Javaobj
31
from .beans import (
3✔
32
    FieldType,
33
    JavaArray,
34
    JavaClassDesc,
35
    JavaEnum,
36
    JavaField,
37
    JavaInstance,
38
    JavaString,
39
    ParsedContent,
40
)
41
from .exceptions import JavaObjError
3✔
42

43
# ------------------------------------------------------------------------------
44

45
# Module version
46
__version_info__ = (0, 5, 0)
3✔
47
__version__ = ".".join(str(x) for x in __version_info__)
3✔
48

49
# Documentation strings format
50
__docformat__ = "restructuredtext en"
3✔
51

52
# ------------------------------------------------------------------------------
53

54
__all__ = [
3✔
55
    "v2_to_v3",
56
    "v1_to_v3",
57
    "V1CompatMixin",
58
    "V2CompatMixin",
59
]
60

61

62
# ------------------------------------------------------------------------------
63
# v2 → v3 adapter
64
# ------------------------------------------------------------------------------
65

66

67
def v2_to_v3(v2_obj: Any) -> ParsedContent:
3✔
68
    """
69
    Converts a javaobj **v2** top-level object to the nearest v3 equivalent.
70

71
    For types that map directly (e.g. ``javaobj.v2.beans.JavaInstance`` →
72
    ``javaobj.v3.beans.JavaInstance``) the fields are copied shallowly.
73
    Nested objects are **not** recursively converted — only the top-level
74
    wrapper is adapted.
75

76
    :param v2_obj: A parsed object returned by :func:`javaobj.v2.load` or
77
                   :func:`javaobj.v2.loads`.
78
    :return: The v3 equivalent object.
79
    :raises JavaObjError: If the type cannot be mapped.
80
    """
81
    try:
3✔
82
        from javaobj.v2.beans import (
3✔
83
            JavaArray as V2Array,
84
        )
85
        from javaobj.v2.beans import (
3✔
86
            JavaEnum as V2Enum,
87
        )
88
        from javaobj.v2.beans import (  # type: ignore[import]
3✔
89
            JavaInstance as V2Instance,
90
        )
91
        from javaobj.v2.beans import (
3✔
92
            JavaString as V2String,
93
        )
NEW
94
    except ImportError as exc:
×
NEW
95
        raise JavaObjError("javaobj.v2 is not available; cannot perform v2 → v3 conversion") from exc
×
96

97
    if isinstance(v2_obj, V2String):
3✔
98
        return JavaString(handle=v2_obj.handle, value=v2_obj.value)
3✔
99

100
    if isinstance(v2_obj, V2Enum):
3✔
NEW
101
        cd = _v2_classdesc_to_v3(v2_obj.classdesc)
×
NEW
102
        constant = JavaString(handle=v2_obj.constant.handle, value=v2_obj.constant.value)
×
NEW
103
        return JavaEnum(handle=v2_obj.handle, classdesc=cd, constant=constant)
×
104

105
    if isinstance(v2_obj, V2Array):
3✔
106
        cd = _v2_classdesc_to_v3(v2_obj.classdesc)
3✔
107
        data: bytes | list[Any] = (
3✔
108
            bytes(v2_obj.data)
109
            if v2_obj.field_type and v2_obj.field_type.value == FieldType.BYTE.value
110
            else list(v2_obj.data)
111
        )
112
        return JavaArray(
3✔
113
            handle=v2_obj.handle,
114
            classdesc=cd,
115
            element_type=FieldType(v2_obj.field_type.value),
116
            data=data,
117
        )
118

119
    if isinstance(v2_obj, V2Instance):
3✔
120
        return _v2_instance_to_v3(v2_obj)
3✔
121

122
    raise JavaObjError(f"Cannot convert v2 object of type {type(v2_obj).__name__!r} to v3")
3✔
123

124

125
def _v2_classdesc_to_v3(v2_cd: Any) -> JavaClassDesc:
3✔
126
    """Shallow conversion of a v2 JavaClassDesc to a v3 JavaClassDesc."""
127
    fields = [
3✔
128
        JavaField(
129
            type=FieldType(f.type.value),
130
            name=f.name,
131
            class_name=f.class_name.value if f.class_name else None,
132
        )
133
        for f in (v2_cd.fields or [])
134
    ]
135
    return JavaClassDesc(
3✔
136
        handle=v2_cd.handle,
137
        name=v2_cd.name or "",
138
        serial_version_uid=v2_cd.serial_version_uid,
139
        desc_flags=v2_cd.desc_flags,
140
        fields=fields,
141
    )
142

143

144
def _v2_instance_to_v3(v2_inst: Any) -> JavaInstance:
3✔
145
    """Shallow conversion of a v2 JavaInstance to a v3 JavaInstance."""
146
    cd = _v2_classdesc_to_v3(v2_inst.classdesc) if v2_inst.classdesc else None
3✔
147

148
    v3_inst = JavaInstance()
3✔
149
    v3_inst.handle = v2_inst.handle
3✔
150
    v3_inst.classdesc = cd  # type: ignore[assignment]
3✔
151
    v3_inst.is_exception = getattr(v2_inst, "is_exception", False)
3✔
152

153
    # Copy field_data with converted keys
154
    # v2 field_data is {JavaClassDesc: {JavaField: value}}, same nesting as v3
155
    if cd is not None:
3✔
156
        v3_field_data: dict[JavaClassDesc, dict[JavaField, Any]] = {}
3✔
157
        for v2_cd_key, v2_fields_dict in v2_inst.field_data.items():
3✔
158
            v3_cd_key = _v2_classdesc_to_v3(v2_cd_key)
3✔
159
            v3_fields: dict[JavaField, Any] = {}
3✔
160
            for v2_f, val in v2_fields_dict.items():
3✔
161
                v3_f = JavaField(
3✔
162
                    type=FieldType(v2_f.type.value),
163
                    name=v2_f.name,
164
                    class_name=(v2_f.class_name.value if v2_f.class_name else None),
165
                )
166
                v3_fields[v3_f] = val
3✔
167
            v3_field_data[v3_cd_key] = v3_fields
3✔
168
        v3_inst.field_data = v3_field_data
3✔
169

170
    return v3_inst
3✔
171

172

173
# ------------------------------------------------------------------------------
174
# v1 → v3 adapter
175
# ------------------------------------------------------------------------------
176

177

178
def v1_to_v3(v1_obj: Any) -> ParsedContent:
3✔
179
    """
180
    Converts a javaobj **v1** top-level object to a v3 equivalent.
181

182
    :param v1_obj: A parsed object returned by the top-level
183
                   :func:`javaobj.load` / :func:`javaobj.loads` (v1 API).
184
    :return: The v3 equivalent object.
185
    :raises JavaObjError: If the type cannot be mapped.
186
    """
187
    try:
3✔
188
        from javaobj.v1.beans import (
3✔
189
            JavaArray as V1Array,
190
        )
191
        from javaobj.v1.beans import (
3✔
192
            JavaEnum as V1Enum,
193
        )
194
        from javaobj.v1.beans import (  # type: ignore[import]
3✔
195
            JavaObject,
196
        )
197
        from javaobj.v1.beans import (
3✔
198
            JavaString as V1String,
199
        )
NEW
200
    except ImportError as exc:
×
NEW
201
        raise JavaObjError("javaobj.v1 is not available; cannot perform v1 → v3 conversion") from exc
×
202

203
    if isinstance(v1_obj, V1String):
3✔
NEW
204
        return JavaString(handle=0, value=str(v1_obj))
×
205

206
    if isinstance(v1_obj, V1Enum):
3✔
NEW
207
        cd = _v1_classdesc_to_v3(v1_obj.classdesc)
×
NEW
208
        constant = JavaString(handle=0, value=str(v1_obj.constant))
×
NEW
209
        return JavaEnum(handle=0, classdesc=cd, constant=constant)
×
210

211
    if isinstance(v1_obj, V1Array):
3✔
NEW
212
        return _v1_array_to_v3(v1_obj)
×
213

214
    if isinstance(v1_obj, JavaObject):
3✔
215
        return _v1_object_to_v3(v1_obj)
3✔
216

217
    raise JavaObjError(f"Cannot convert v1 object of type {type(v1_obj).__name__!r} to v3")
3✔
218

219

220
def _v1_classdesc_to_v3(v1_cd: Any) -> JavaClassDesc:
3✔
221
    """Shallow conversion of a v1 JavaClass to a v3 JavaClassDesc."""
222
    fields = [
3✔
223
        JavaField(
224
            # fields_types contains full class descriptors like 'Ljava/lang/String;'
225
            # or single-char primitives like 'B', 'I', etc.  The first character
226
            # always encodes the FieldType (e.g. 'L' → OBJECT, 'B' → BYTE).
227
            type=FieldType(ord(str(t)[0])),
228
            name=n,
229
        )
230
        for n, t in zip(
231
            getattr(v1_cd, "fields_names", []),
232
            getattr(v1_cd, "fields_types", []),
233
        )
234
    ]
235
    return JavaClassDesc(
3✔
236
        handle=0,
237
        name=getattr(v1_cd, "name", "") or "",
238
        serial_version_uid=getattr(v1_cd, "serialVersionUID", 0) or 0,
239
        desc_flags=getattr(v1_cd, "flags", 0) or 0,
240
        fields=fields,
241
    )
242

243

244
def _v1_object_to_v3(v1_obj: Any) -> JavaInstance:
3✔
245
    """Shallow conversion of a v1 JavaObject to a v3 JavaInstance."""
246
    cd = _v1_classdesc_to_v3(v1_obj.classdesc) if v1_obj.classdesc else None
3✔
247

248
    v3_inst = JavaInstance()
3✔
249
    v3_inst.handle = 0
3✔
250
    v3_inst.classdesc = cd  # type: ignore[assignment]
3✔
251
    return v3_inst
3✔
252

253

254
def _v1_array_to_v3(v1_arr: Any) -> JavaArray:
3✔
255
    """Shallow conversion of a v1 JavaArray to a v3 JavaArray."""
NEW
256
    cd = _v1_classdesc_to_v3(v1_arr.classdesc) if v1_arr.classdesc else None
×
257

258
    raw_data: bytes | list[Any]
NEW
259
    if isinstance(v1_arr, (bytes, bytearray)):
×
NEW
260
        raw_data = bytes(v1_arr)
×
261
    else:
NEW
262
        raw_data = list(v1_arr)
×
263

NEW
264
    return JavaArray(
×
265
        handle=0,
266
        classdesc=cd,  # type: ignore[arg-type]
267
        element_type=FieldType.OBJECT,
268
        data=raw_data,
269
    )
270

271

272
# ------------------------------------------------------------------------------
273
# Convenience mixins for custom transformer classes
274
# ------------------------------------------------------------------------------
275

276

277
class V2CompatMixin:
3✔
278
    """
279
    Mixin for v3 transformer subclasses that need a v2-style
280
    ``load_from_instance(indent=0)`` signature.
281

282
    Usage::
283

284
        class MyTransformer(V2CompatMixin, JavaInstance):
285
            HANDLED_CLASSES = "com.example.MyClass"
286

287
            def load_from_instance(self, indent: int = 0) -> bool:
288
                ...
289
    """
290

291
    def load_from_instance(self, indent: int = 0) -> bool:  # type: ignore[override]
3✔
292
        """v2-compatible hook; delegates to the v3 no-argument version."""
NEW
293
        return self._load_from_instance_v3()
×
294

295
    def _load_from_instance_v3(self) -> bool:
3✔
296
        """Override this in subclasses to implement the actual loading."""
NEW
297
        return False
×
298

299

300
class V1CompatMixin:
3✔
301
    """
302
    Mixin that adds a ``classdesc`` shim so that v1-style transformer code
303
    that accesses ``obj.classdesc.name`` works unchanged on v3 instances.
304
    """
305

306
    # No-op: JavaInstance already has a classdesc attribute in v3.
307
    pass
3✔
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

© 2026 Coveralls, Inc