# javaobj-py3
[![Latest Version](https://img.shields.io/pypi/v/javaobj-py3.svg)](https://pypi.python.org/pypi/javaobj-py3/)
[![License](https://img.shields.io/pypi/l/javaobj-py3.svg)](https://pypi.python.org/pypi/javaobj-py3/)
[![CI Build](https://github.com/tcalmant/python-javaobj/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/tcalmant/python-javaobj/actions/workflows/build.yml)
[![Coveralls status](https://coveralls.io/repos/tcalmant/python-javaobj/badge.svg?branch=master)](https://coveralls.io/r/tcalmant/python-javaobj?branch=master)
*python-javaobj* is a python library that provides functions for reading and
writing (writing is WIP currently) Java objects serialized or will be
deserialized by `ObjectOutputStream`. This form of object representation is a
standard data interchange format in Java world.
The `javaobj` module exposes an API familiar to users of the standard library
`marshal`, `pickle` and `json` modules.
## About this repository
This project is a fork of *python-javaobj* by Volodymyr Buell, originally from
[Google Code](http://code.google.com/p/python-javaobj/) and now hosted on
[GitHub](https://github.com/vbuell/python-javaobj).
This fork intends to work both on Python 2.7 and Python 3.4+.
## Compatibility Warnings
### New implementation of the parser
| Implementations | Version |
|-----------------|----------|
| `v1`, `v2` | `0.4.0+` |
Since version 0.4.0, two implementations of the parser are available:
* `v1`: the *classic* implementation of `javaobj`, with a work in progress
implementation of a writer.
* `v2`: the *new* implementation, which is a port of the Java project
[`jdeserialize`](https://github.com/frohoff/jdeserialize/),
with support of the object transformer (with a new API) and of the `numpy`
arrays loading.
You can use the `v1` parser to ensure that the behaviour of your scripts
doesn't change and to keep the ability to write down files.
You can use the `v2` parser for new developments
*which won't require marshalling* and as a *fallback* if the `v1`
fails to parse a file.
### Object transformers V1
| Implementations | Version |
|-----------------|----------|
| `v1` | `0.2.0+` |
As of version 0.2.0, the notion of *object transformer* from the original
project as been replaced by an *object creator*.
The *object creator* is called before the deserialization.
This allows to store the reference of the converted object before deserializing
it, and avoids a mismatch between the referenced object and the transformed one.
### Object transformers V2
| Implementations | Version |
|-----------------|----------|
| `v2` | `0.4.0+` |
The `v2` implementation provides a new API for the object transformers.
Please look at the *Usage (V2)* section in this file.
### Bytes arrays
| Implementations | Version |
|-----------------|----------|
| `v1` | `0.2.3+` |
As of version 0.2.3, bytes arrays are loaded as a `bytes` object instead of
an array of integers.
### Custom Transformer
| Implementations | Version |
|-----------------|----------|
| `v2` | `0.4.2+` |
A new transformer API has been proposed to handle objects written with a custom
Java writer.
You can find a sample usage in the *Custom Transformer* section in this file.
## Features
* Java object instance un-marshalling
* Java classes un-marshalling
* Primitive values un-marshalling
* Automatic conversion of Java Collections to python ones
(`HashMap` => `dict`, `ArrayList` => `list`, etc.)
* Basic marshalling of simple Java objects (`v1` implementation only)
* Automatically uncompresses GZipped files
## Requirements
* Python >= 2.7 or Python >= 3.4
* `enum34` and `typing` when using Python <= 3.4 (installable with `pip`)
* Maven 2+ (for building test data of serialized objects.
You can skip it if you do not plan to run `tests.py`)
## Usage (V1 implementation)
Un-marshalling of Java serialised object:
```python
import javaobj
with open("obj5.ser", "rb") as fd:
jobj = fd.read()
pobj = javaobj.loads(jobj)
print(pobj)
```
Or, you can use `JavaObjectUnmarshaller` object directly:
```python
import javaobj
with open("objCollections.ser", "rb") as fd:
marshaller = javaobj.JavaObjectUnmarshaller(fd)
pobj = marshaller.readObject()
print(pobj.value, "should be", 17)
print(pobj.next, "should be", True)
pobj = marshaller.readObject()
```
**Note:** The objects and methods provided by `javaobj` module are shortcuts
to the `javaobj.v1` package, for Compatibility purpose.
It is **recommended** to explicitly import methods and classes from the `v1`
(or `v2`) package when writing new code, in order to be sure that your code
won't need import updates in the future.
## Usage (V2 implementation)
The following methods are provided by the `javaobj.v2` package:
* `load(fd, *transformers, use_numpy_arrays=False)`:
Parses the content of the given file descriptor, opened in binary mode (`rb`).
The method accepts a list of custom object transformers. The default object
transformer is always added to the list.
The `use_numpy_arrays` flag indicates that the arrays of primitive type
elements must be loaded using `numpy` (if available) instead of using the
standard parsing technic.
* `loads(bytes, *transformers, use_numpy_arrays=False)`:
This the a shortcut to the `load()` method, providing it the binary data
using a `BytesIO` object.
**Note:** The V2 parser doesn't have the marshalling capability.
Sample usage:
```python
import javaobj.v2 as javaobj
with open("obj5.ser", "rb") as fd:
pobj = javaobj.load(fd)
print(pobj.dump())
```
### Object Transformer
An object transformer can be called during the parsing of a Java object
instance or while loading an array.
The Java object instance parsing works in two main steps:
1. The transformer is called to create an instance of a bean that inherits
`JavaInstance`.
1. The latter bean is then called:
* When the object is written with a custom block data
* After the fields and annotations have been parsed, to update the content
of the Python bean.
Here is an example for a Java `HashMap` object. You can look at the code of
the `javaobj.v2.transformer` module to see the whole implementation.
```python
class JavaMap(dict, javaobj.v2.beans.JavaInstance):
"""
Inherits from dict for Python usage, JavaInstance for parsing purpose
"""
def __init__(self):
# Don't forget to call both constructors
dict.__init__(self)
JavaInstance.__init__(self)
def load_from_blockdata(self, parser, reader, indent=0):
"""
Reads content stored in a block data.
This method is called only if the class description has both the
`SC_EXTERNALIZABLE` and `SC_BLOCK_DATA` flags set.
The stream parsing will stop and fail if this method returns False.
:param parser: The JavaStreamParser in use
:param reader: The underlying data stream reader
:param indent: Indentation to use in logs
:return: True on success, False on error
"""
# This kind of class is not supposed to have the SC_BLOCK_DATA flag set
return False
def load_from_instance(self, indent=0):
# type: (int) -> bool
"""
Load content from the parsed instance object.
This method is called after the block data (if any), the fields and
the annotations have been loaded.
:param indent: Indentation to use while logging
:return: True on success (currently ignored)
"""
# Maps have their content in their annotations
for cd, annotations in self.annotations.items():
# Annotations are associated to their definition class
if cd.name == "java.util.HashMap":
# We are in the annotation created by the handled class
# Group annotation elements 2 by 2
# (storage is: key, value, key, value, ...)
args = [iter(annotations[1:])] * 2
for key, value in zip(*args):
self[key] = value
# Job done
return True
# Couldn't load the data
return False
class MapObjectTransformer(javaobj.v2.api.ObjectTransformer):
"""
Creates a JavaInstance object with custom loading methods for the
classes it can handle
"""
def create_instance(self, classdesc):
# type: (JavaClassDesc) -> Optional[JavaInstance]
"""
Transforms a parsed Java object into a Python object
:param classdesc: The description of a Java class
:return: The Python form of the object, or the original JavaObject
"""
if classdesc.name == "java.util.HashMap":
# We can handle this class description
return JavaMap()
else:
# Return None if the class is not handled
return None
```
### Custom Object Transformer
The custom transformer is called when the class is not handled by the default
object transformer.
A custom object transformer still inherits from the `ObjectTransformer` class,
but it also implements the `load_custom_writeObject` method.
The sample given here is used in the unit tests.
#### Java sample
On the Java side, we create various classes and write them as we wish:
```java
class CustomClass implements Serializable {
private static final long serialVersionUID = 1;
public void start(ObjectOutputStream out) throws Exception {
this.writeObject(out);
}
private void writeObject(ObjectOutputStream out) throws IOException {
CustomWriter custom = new CustomWriter(42);
out.writeObject(custom);
out.flush();
}
}
class RandomChild extends Random {
private static final long serialVersionUID = 1;
private int num = 1;
private double doub = 4.5;
RandomChild(int seed) {
super(seed);
}
}
class CustomWriter implements Serializable {
protected RandomChild custom_obj;
CustomWriter(int seed) {
custom_obj = new RandomChild(seed);
}
private static final long serialVersionUID = 1;
private static final int CURRENT_SERIAL_VERSION = 0;
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeInt(CURRENT_SERIAL_VERSION);
out.writeObject(custom_obj);
}
}
```
An here is a sample writing of that kind of object:
```java
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("custom_objects.ser"));
CustomClass writer = new CustomClass();
writer.start(oos);
oos.flush();
oos.close();
```
#### Python sample
On the Python side, the first step is to define the custom transformers.
They are children of the `javaobj.v2.transformers.ObjectTransformer` class.
```python
class BaseTransformer(javaobj.v2.transformers.ObjectTransformer):
"""
Creates a JavaInstance object with custom loading methods for the
classes it can handle
"""
def __init__(self, handled_classes=None):
self.instance = None
self.handled_classes = handled_classes or {}
def create_instance(self, classdesc):
"""
Transforms a parsed Java object into a Python object
:param classdesc: The description of a Java class
:return: The Python form of the object, or the original JavaObject
"""
if classdesc.name in self.handled_classes:
self.instance = self.handled_classes[classdesc.name]()
return self.instance
return None
class RandomChildTransformer(BaseTransformer):
def __init__(self):
super(RandomChildTransformer, self).__init__(
{"RandomChild": RandomChildInstance}
)
class CustomWriterTransformer(BaseTransformer):
def __init__(self):
super(CustomWriterTransformer, self).__init__(
{"CustomWriter": CustomWriterInstance}
)
class JavaRandomTransformer(BaseTransformer):
def __init__(self):
super(JavaRandomTransformer, self).__init__()
self.name = "java.util.Random"
self.field_names = ["haveNextNextGaussian", "nextNextGaussian", "seed"]
self.field_types = [
javaobj.v2.beans.FieldType.BOOLEAN,
javaobj.v2.beans.FieldType.DOUBLE,
javaobj.v2.beans.FieldType.LONG,
]
def load_custom_writeObject(self, parser, reader, name):
if name != self.name:
return None
fields = []
values = []
for f_name, f_type in zip(self.field_names, self.field_types):
values.append(parser._read_field_value(f_type))
fields.append(javaobj.beans.JavaField(f_type, f_name))
class_desc = javaobj.beans.JavaClassDesc(
javaobj.beans.ClassDescType.NORMALCLASS
)
class_desc.name = self.name
class_desc.desc_flags = javaobj.beans.ClassDataType.EXTERNAL_CONTENTS
class_desc.fields = fields
class_desc.field_data = values
return class_desc
```
Second step is defining the representation of the instances, where the real
object loading occurs. Those classes inherit from
`javaobj.v2.beans.JavaInstance`.
```python
class CustomWriterInstance(javaobj.v2.beans.JavaInstance):
def __init__(self):
javaobj.v2.beans.JavaInstance.__init__(self)
def load_from_instance(self):
"""
Updates the content of this instance
from its parsed fields and annotations
:return: True on success, False on error
"""
if self.classdesc and self.classdesc in self.annotations:
# Here, we known there is something written before the fields,
# even if it's not declared in the class description
fields = ["int_not_in_fields"] + self.classdesc.fields_names
raw_data = self.annotations[self.classdesc]
int_not_in_fields = struct.unpack(
">i", BytesIO(raw_data[0].data).read(4)
)[0]
custom_obj = raw_data[1]
values = [int_not_in_fields, custom_obj]
self.field_data = dict(zip(fields, values))
return True
return False
class RandomChildInstance(javaobj.v2.beans.JavaInstance):
def load_from_instance(self):
"""
Updates the content of this instance
from its parsed fields and annotations
:return: True on success, False on error
"""
if self.classdesc and self.classdesc in self.field_data:
fields = self.classdesc.fields_names
values = [
self.field_data[self.classdesc][self.classdesc.fields[i]]
for i in range(len(fields))
]
self.field_data = dict(zip(fields, values))
if (
self.classdesc.super_class
and self.classdesc.super_class in self.annotations
):
super_class = self.annotations[self.classdesc.super_class][0]
self.annotations = dict(
zip(super_class.fields_names, super_class.field_data)
)
return True
return False
```
Finally we can use the transformers in the loading process.
Note that even if it is not explicitly given, the `DefaultObjectTransformer`
will be also be used, as it is added automatically by `javaobj` if it is
missing from the given list.
```python
# Load the object using those transformers
transformers = [
CustomWriterTransformer(),
RandomChildTransformer(),
JavaRandomTransformer()
]
pobj = javaobj.loads("custom_objects.ser", *transformers)
# Here we show a field that isn't visible from the class description
# The field belongs to the class but it's not serialized by default because
# it's static. See: https://stackoverflow.com/a/16477421/12621168
print(pobj.field_data["int_not_in_fields"])
```
Raw data
{
"_id": null,
"home_page": "https://github.com/tcalmant/python-javaobj",
"name": "javaobj-py3",
"maintainer": "Thomas Calmant",
"docs_url": null,
"requires_python": null,
"maintainer_email": "thomas.calmant@gmail.com",
"keywords": "python java marshalling serialization",
"author": "Volodymyr Buell",
"author_email": "vbuell@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/28/56/38cbec76b6ed625f9b5e9ce7ada7d1a63f02954bab9b2a44151ba17bd578/javaobj-py3-0.4.4.tar.gz",
"platform": null,
"description": "# javaobj-py3\r\n\r\n[![Latest Version](https://img.shields.io/pypi/v/javaobj-py3.svg)](https://pypi.python.org/pypi/javaobj-py3/)\r\n[![License](https://img.shields.io/pypi/l/javaobj-py3.svg)](https://pypi.python.org/pypi/javaobj-py3/)\r\n[![CI Build](https://github.com/tcalmant/python-javaobj/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/tcalmant/python-javaobj/actions/workflows/build.yml)\r\n[![Coveralls status](https://coveralls.io/repos/tcalmant/python-javaobj/badge.svg?branch=master)](https://coveralls.io/r/tcalmant/python-javaobj?branch=master)\r\n\r\n*python-javaobj* is a python library that provides functions for reading and\r\nwriting (writing is WIP currently) Java objects serialized or will be\r\ndeserialized by `ObjectOutputStream`. This form of object representation is a\r\nstandard data interchange format in Java world.\r\n\r\nThe `javaobj` module exposes an API familiar to users of the standard library\r\n`marshal`, `pickle` and `json` modules.\r\n\r\n## About this repository\r\n\r\nThis project is a fork of *python-javaobj* by Volodymyr Buell, originally from\r\n[Google Code](http://code.google.com/p/python-javaobj/) and now hosted on\r\n[GitHub](https://github.com/vbuell/python-javaobj).\r\n\r\nThis fork intends to work both on Python 2.7 and Python 3.4+.\r\n\r\n## Compatibility Warnings\r\n\r\n### New implementation of the parser\r\n\r\n| Implementations | Version |\r\n|-----------------|----------|\r\n| `v1`, `v2` | `0.4.0+` |\r\n\r\nSince version 0.4.0, two implementations of the parser are available:\r\n\r\n* `v1`: the *classic* implementation of `javaobj`, with a work in progress\r\n implementation of a writer.\r\n* `v2`: the *new* implementation, which is a port of the Java project\r\n [`jdeserialize`](https://github.com/frohoff/jdeserialize/),\r\n with support of the object transformer (with a new API) and of the `numpy`\r\n arrays loading.\r\n\r\nYou can use the `v1` parser to ensure that the behaviour of your scripts\r\ndoesn't change and to keep the ability to write down files.\r\n\r\nYou can use the `v2` parser for new developments\r\n*which won't require marshalling* and as a *fallback* if the `v1`\r\nfails to parse a file.\r\n\r\n### Object transformers V1\r\n\r\n| Implementations | Version |\r\n|-----------------|----------|\r\n| `v1` | `0.2.0+` |\r\n\r\nAs of version 0.2.0, the notion of *object transformer* from the original\r\nproject as been replaced by an *object creator*.\r\n\r\nThe *object creator* is called before the deserialization.\r\nThis allows to store the reference of the converted object before deserializing\r\nit, and avoids a mismatch between the referenced object and the transformed one.\r\n\r\n### Object transformers V2\r\n\r\n| Implementations | Version |\r\n|-----------------|----------|\r\n| `v2` | `0.4.0+` |\r\n\r\nThe `v2` implementation provides a new API for the object transformers.\r\nPlease look at the *Usage (V2)* section in this file.\r\n\r\n### Bytes arrays\r\n\r\n| Implementations | Version |\r\n|-----------------|----------|\r\n| `v1` | `0.2.3+` |\r\n\r\nAs of version 0.2.3, bytes arrays are loaded as a `bytes` object instead of\r\nan array of integers.\r\n\r\n### Custom Transformer\r\n\r\n| Implementations | Version |\r\n|-----------------|----------|\r\n| `v2` | `0.4.2+` |\r\n\r\nA new transformer API has been proposed to handle objects written with a custom\r\nJava writer.\r\nYou can find a sample usage in the *Custom Transformer* section in this file.\r\n\r\n## Features\r\n\r\n* Java object instance un-marshalling\r\n* Java classes un-marshalling\r\n* Primitive values un-marshalling\r\n* Automatic conversion of Java Collections to python ones\r\n (`HashMap` => `dict`, `ArrayList` => `list`, etc.)\r\n* Basic marshalling of simple Java objects (`v1` implementation only)\r\n* Automatically uncompresses GZipped files\r\n\r\n## Requirements\r\n\r\n* Python >= 2.7 or Python >= 3.4\r\n* `enum34` and `typing` when using Python <= 3.4 (installable with `pip`)\r\n* Maven 2+ (for building test data of serialized objects.\r\n You can skip it if you do not plan to run `tests.py`)\r\n\r\n## Usage (V1 implementation)\r\n\r\nUn-marshalling of Java serialised object:\r\n\r\n```python\r\nimport javaobj\r\n\r\nwith open(\"obj5.ser\", \"rb\") as fd:\r\n jobj = fd.read()\r\n\r\npobj = javaobj.loads(jobj)\r\nprint(pobj)\r\n```\r\n\r\nOr, you can use `JavaObjectUnmarshaller` object directly:\r\n\r\n```python\r\nimport javaobj\r\n\r\nwith open(\"objCollections.ser\", \"rb\") as fd:\r\n marshaller = javaobj.JavaObjectUnmarshaller(fd)\r\n pobj = marshaller.readObject()\r\n\r\n print(pobj.value, \"should be\", 17)\r\n print(pobj.next, \"should be\", True)\r\n\r\n pobj = marshaller.readObject()\r\n```\r\n\r\n**Note:** The objects and methods provided by `javaobj` module are shortcuts\r\nto the `javaobj.v1` package, for Compatibility purpose.\r\nIt is **recommended** to explicitly import methods and classes from the `v1`\r\n(or `v2`) package when writing new code, in order to be sure that your code\r\nwon't need import updates in the future.\r\n\r\n\r\n## Usage (V2 implementation)\r\n\r\nThe following methods are provided by the `javaobj.v2` package:\r\n\r\n* `load(fd, *transformers, use_numpy_arrays=False)`:\r\n Parses the content of the given file descriptor, opened in binary mode (`rb`).\r\n The method accepts a list of custom object transformers. The default object\r\n transformer is always added to the list.\r\n\r\n The `use_numpy_arrays` flag indicates that the arrays of primitive type\r\n elements must be loaded using `numpy` (if available) instead of using the\r\n standard parsing technic.\r\n\r\n* `loads(bytes, *transformers, use_numpy_arrays=False)`:\r\n This the a shortcut to the `load()` method, providing it the binary data\r\n using a `BytesIO` object.\r\n\r\n**Note:** The V2 parser doesn't have the marshalling capability.\r\n\r\nSample usage:\r\n\r\n```python\r\nimport javaobj.v2 as javaobj\r\n\r\nwith open(\"obj5.ser\", \"rb\") as fd:\r\n pobj = javaobj.load(fd)\r\n\r\nprint(pobj.dump())\r\n```\r\n\r\n### Object Transformer\r\n\r\nAn object transformer can be called during the parsing of a Java object\r\ninstance or while loading an array.\r\n\r\nThe Java object instance parsing works in two main steps:\r\n\r\n1. The transformer is called to create an instance of a bean that inherits\r\n `JavaInstance`.\r\n1. The latter bean is then called:\r\n\r\n * When the object is written with a custom block data\r\n * After the fields and annotations have been parsed, to update the content\r\n of the Python bean.\r\n\r\nHere is an example for a Java `HashMap` object. You can look at the code of\r\nthe `javaobj.v2.transformer` module to see the whole implementation.\r\n\r\n```python\r\nclass JavaMap(dict, javaobj.v2.beans.JavaInstance):\r\n \"\"\"\r\n Inherits from dict for Python usage, JavaInstance for parsing purpose\r\n \"\"\"\r\n def __init__(self):\r\n # Don't forget to call both constructors\r\n dict.__init__(self)\r\n JavaInstance.__init__(self)\r\n\r\n def load_from_blockdata(self, parser, reader, indent=0):\r\n \"\"\"\r\n Reads content stored in a block data.\r\n\r\n This method is called only if the class description has both the\r\n `SC_EXTERNALIZABLE` and `SC_BLOCK_DATA` flags set.\r\n\r\n The stream parsing will stop and fail if this method returns False.\r\n\r\n :param parser: The JavaStreamParser in use\r\n :param reader: The underlying data stream reader\r\n :param indent: Indentation to use in logs\r\n :return: True on success, False on error\r\n \"\"\"\r\n # This kind of class is not supposed to have the SC_BLOCK_DATA flag set\r\n return False\r\n\r\n def load_from_instance(self, indent=0):\r\n # type: (int) -> bool\r\n \"\"\"\r\n Load content from the parsed instance object.\r\n\r\n This method is called after the block data (if any), the fields and\r\n the annotations have been loaded.\r\n\r\n :param indent: Indentation to use while logging\r\n :return: True on success (currently ignored)\r\n \"\"\"\r\n # Maps have their content in their annotations\r\n for cd, annotations in self.annotations.items():\r\n # Annotations are associated to their definition class\r\n if cd.name == \"java.util.HashMap\":\r\n # We are in the annotation created by the handled class\r\n # Group annotation elements 2 by 2\r\n # (storage is: key, value, key, value, ...)\r\n args = [iter(annotations[1:])] * 2\r\n for key, value in zip(*args):\r\n self[key] = value\r\n\r\n # Job done\r\n return True\r\n\r\n # Couldn't load the data\r\n return False\r\n\r\nclass MapObjectTransformer(javaobj.v2.api.ObjectTransformer):\r\n \"\"\"\r\n Creates a JavaInstance object with custom loading methods for the\r\n classes it can handle\r\n \"\"\"\r\n def create_instance(self, classdesc):\r\n # type: (JavaClassDesc) -> Optional[JavaInstance]\r\n \"\"\"\r\n Transforms a parsed Java object into a Python object\r\n\r\n :param classdesc: The description of a Java class\r\n :return: The Python form of the object, or the original JavaObject\r\n \"\"\"\r\n if classdesc.name == \"java.util.HashMap\":\r\n # We can handle this class description\r\n return JavaMap()\r\n else:\r\n # Return None if the class is not handled\r\n return None\r\n```\r\n\r\n### Custom Object Transformer\r\n\r\nThe custom transformer is called when the class is not handled by the default\r\nobject transformer.\r\nA custom object transformer still inherits from the `ObjectTransformer` class,\r\nbut it also implements the `load_custom_writeObject` method.\r\n\r\nThe sample given here is used in the unit tests.\r\n\r\n#### Java sample\r\n\r\nOn the Java side, we create various classes and write them as we wish:\r\n\r\n```java\r\nclass CustomClass implements Serializable {\r\n\r\n private static final long serialVersionUID = 1;\r\n\r\n public void start(ObjectOutputStream out) throws Exception {\r\n this.writeObject(out);\r\n }\r\n\r\n private void writeObject(ObjectOutputStream out) throws IOException {\r\n CustomWriter custom = new CustomWriter(42);\r\n out.writeObject(custom);\r\n out.flush();\r\n }\r\n}\r\n\r\nclass RandomChild extends Random {\r\n\r\n private static final long serialVersionUID = 1;\r\n private int num = 1;\r\n private double doub = 4.5;\r\n\r\n RandomChild(int seed) {\r\n super(seed);\r\n }\r\n}\r\n\r\nclass CustomWriter implements Serializable {\r\n protected RandomChild custom_obj;\r\n\r\n CustomWriter(int seed) {\r\n custom_obj = new RandomChild(seed);\r\n }\r\n\r\n private static final long serialVersionUID = 1;\r\n private static final int CURRENT_SERIAL_VERSION = 0;\r\n\r\n private void writeObject(ObjectOutputStream out) throws IOException {\r\n out.writeInt(CURRENT_SERIAL_VERSION);\r\n out.writeObject(custom_obj);\r\n }\r\n}\r\n```\r\n\r\nAn here is a sample writing of that kind of object:\r\n\r\n```java\r\nObjectOutputStream oos = new ObjectOutputStream(\r\n new FileOutputStream(\"custom_objects.ser\"));\r\nCustomClass writer = new CustomClass();\r\nwriter.start(oos);\r\noos.flush();\r\noos.close();\r\n```\r\n\r\n#### Python sample\r\n\r\nOn the Python side, the first step is to define the custom transformers.\r\nThey are children of the `javaobj.v2.transformers.ObjectTransformer` class.\r\n\r\n```python\r\nclass BaseTransformer(javaobj.v2.transformers.ObjectTransformer):\r\n \"\"\"\r\n Creates a JavaInstance object with custom loading methods for the\r\n classes it can handle\r\n \"\"\"\r\n\r\n def __init__(self, handled_classes=None):\r\n self.instance = None\r\n self.handled_classes = handled_classes or {}\r\n\r\n def create_instance(self, classdesc):\r\n \"\"\"\r\n Transforms a parsed Java object into a Python object\r\n\r\n :param classdesc: The description of a Java class\r\n :return: The Python form of the object, or the original JavaObject\r\n \"\"\"\r\n if classdesc.name in self.handled_classes:\r\n self.instance = self.handled_classes[classdesc.name]()\r\n return self.instance\r\n\r\n return None\r\n\r\nclass RandomChildTransformer(BaseTransformer):\r\n def __init__(self):\r\n super(RandomChildTransformer, self).__init__(\r\n {\"RandomChild\": RandomChildInstance}\r\n )\r\n\r\nclass CustomWriterTransformer(BaseTransformer):\r\n def __init__(self):\r\n super(CustomWriterTransformer, self).__init__(\r\n {\"CustomWriter\": CustomWriterInstance}\r\n )\r\n\r\nclass JavaRandomTransformer(BaseTransformer):\r\n def __init__(self):\r\n super(JavaRandomTransformer, self).__init__()\r\n self.name = \"java.util.Random\"\r\n self.field_names = [\"haveNextNextGaussian\", \"nextNextGaussian\", \"seed\"]\r\n self.field_types = [\r\n javaobj.v2.beans.FieldType.BOOLEAN,\r\n javaobj.v2.beans.FieldType.DOUBLE,\r\n javaobj.v2.beans.FieldType.LONG,\r\n ]\r\n\r\n def load_custom_writeObject(self, parser, reader, name):\r\n if name != self.name:\r\n return None\r\n\r\n fields = []\r\n values = []\r\n for f_name, f_type in zip(self.field_names, self.field_types):\r\n values.append(parser._read_field_value(f_type))\r\n fields.append(javaobj.beans.JavaField(f_type, f_name))\r\n\r\n class_desc = javaobj.beans.JavaClassDesc(\r\n javaobj.beans.ClassDescType.NORMALCLASS\r\n )\r\n class_desc.name = self.name\r\n class_desc.desc_flags = javaobj.beans.ClassDataType.EXTERNAL_CONTENTS\r\n class_desc.fields = fields\r\n class_desc.field_data = values\r\n return class_desc\r\n```\r\n\r\nSecond step is defining the representation of the instances, where the real\r\nobject loading occurs. Those classes inherit from\r\n`javaobj.v2.beans.JavaInstance`.\r\n\r\n```python\r\nclass CustomWriterInstance(javaobj.v2.beans.JavaInstance):\r\n def __init__(self):\r\n javaobj.v2.beans.JavaInstance.__init__(self)\r\n\r\n def load_from_instance(self):\r\n \"\"\"\r\n Updates the content of this instance\r\n from its parsed fields and annotations\r\n :return: True on success, False on error\r\n \"\"\"\r\n if self.classdesc and self.classdesc in self.annotations:\r\n # Here, we known there is something written before the fields,\r\n # even if it's not declared in the class description\r\n fields = [\"int_not_in_fields\"] + self.classdesc.fields_names\r\n raw_data = self.annotations[self.classdesc]\r\n int_not_in_fields = struct.unpack(\r\n \">i\", BytesIO(raw_data[0].data).read(4)\r\n )[0]\r\n custom_obj = raw_data[1]\r\n values = [int_not_in_fields, custom_obj]\r\n self.field_data = dict(zip(fields, values))\r\n return True\r\n\r\n return False\r\n\r\n\r\nclass RandomChildInstance(javaobj.v2.beans.JavaInstance):\r\n def load_from_instance(self):\r\n \"\"\"\r\n Updates the content of this instance\r\n from its parsed fields and annotations\r\n :return: True on success, False on error\r\n \"\"\"\r\n if self.classdesc and self.classdesc in self.field_data:\r\n fields = self.classdesc.fields_names\r\n values = [\r\n self.field_data[self.classdesc][self.classdesc.fields[i]]\r\n for i in range(len(fields))\r\n ]\r\n self.field_data = dict(zip(fields, values))\r\n if (\r\n self.classdesc.super_class\r\n and self.classdesc.super_class in self.annotations\r\n ):\r\n super_class = self.annotations[self.classdesc.super_class][0]\r\n self.annotations = dict(\r\n zip(super_class.fields_names, super_class.field_data)\r\n )\r\n return True\r\n\r\n return False\r\n```\r\n\r\nFinally we can use the transformers in the loading process.\r\nNote that even if it is not explicitly given, the `DefaultObjectTransformer`\r\nwill be also be used, as it is added automatically by `javaobj` if it is\r\nmissing from the given list.\r\n\r\n```python\r\n# Load the object using those transformers\r\ntransformers = [\r\n CustomWriterTransformer(),\r\n RandomChildTransformer(),\r\n JavaRandomTransformer()\r\n]\r\npobj = javaobj.loads(\"custom_objects.ser\", *transformers)\r\n\r\n# Here we show a field that isn't visible from the class description\r\n# The field belongs to the class but it's not serialized by default because\r\n# it's static. See: https://stackoverflow.com/a/16477421/12621168\r\nprint(pobj.field_data[\"int_not_in_fields\"])\r\n```\r\n",
"bugtrack_url": null,
"license": "Apache License 2.0",
"summary": "Module for serializing and de-serializing Java objects.",
"version": "0.4.4",
"project_urls": {
"Homepage": "https://github.com/tcalmant/python-javaobj"
},
"split_keywords": [
"python",
"java",
"marshalling",
"serialization"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "685e94afe8aaae8d5d1be025acb4810788e58318f7cde266eaba77fe5016a1a6",
"md5": "413e206c7aed75ad5fc7a14fd7ab9889",
"sha256": "d7d676fe71825f6c17024df6791b80b7cc30ef40b61100f4ea3961af063f79b6"
},
"downloads": -1,
"filename": "javaobj_py3-0.4.4-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "413e206c7aed75ad5fc7a14fd7ab9889",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": null,
"size": 57149,
"upload_time": "2024-04-07T19:25:55",
"upload_time_iso_8601": "2024-04-07T19:25:55.782639Z",
"url": "https://files.pythonhosted.org/packages/68/5e/94afe8aaae8d5d1be025acb4810788e58318f7cde266eaba77fe5016a1a6/javaobj_py3-0.4.4-py2.py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "285638cbec76b6ed625f9b5e9ce7ada7d1a63f02954bab9b2a44151ba17bd578",
"md5": "4a3e482299ef06c5f02100be119844ab",
"sha256": "e4e3257ef2cf81a3339787a4d5cf924e54c91f095a723f6d2584dae61d4396ed"
},
"downloads": -1,
"filename": "javaobj-py3-0.4.4.tar.gz",
"has_sig": false,
"md5_digest": "4a3e482299ef06c5f02100be119844ab",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 82052,
"upload_time": "2024-04-07T19:25:57",
"upload_time_iso_8601": "2024-04-07T19:25:57.724211Z",
"url": "https://files.pythonhosted.org/packages/28/56/38cbec76b6ed625f9b5e9ce7ada7d1a63f02954bab9b2a44151ba17bd578/javaobj-py3-0.4.4.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-04-07 19:25:57",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "tcalmant",
"github_project": "python-javaobj",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"requirements": [
{
"name": "enum34",
"specs": []
},
{
"name": "typing",
"specs": []
}
],
"lcname": "javaobj-py3"
}