Skip to content

Commit 29cb6c3

Browse files
committed
Added custom transformers in the README
1 parent ddc9664 commit 29cb6c3

File tree

1 file changed

+204
-0
lines changed

1 file changed

+204
-0
lines changed

README.rst

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,15 @@ Bytes arrays
9090
As of version 0.2.3, bytes arrays are loaded as a ``bytes`` object instead of
9191
an array of integers.
9292

93+
Custom Transformer
94+
------------------
95+
96+
:Implementations: ``v2``
97+
:Version: ``0.4.1+``
98+
99+
A new transformer API has been proposed to handle objects written with a custom
100+
Java writer.
101+
You can find a sample usage in the *Custom Transformer* section in this file.
93102

94103
Features
95104
========
@@ -273,3 +282,198 @@ the ``javaobj.v2.transformer`` module to see the whole implementation.
273282
else:
274283
# Return None if the class is not handled
275284
return None
285+
286+
Custom Transformer
287+
------------------
288+
289+
The custom transformer is called when the class is not handled by the default
290+
object transformer.
291+
292+
293+
The sample given here is used in the unit tests.
294+
295+
On the Java side, we create various classes and write them as we wish:
296+
297+
.. code-block:: java
298+
299+
class CustomClass implements Serializable {
300+
private static final long serialVersionUID = 1;
301+
302+
public void start(ObjectOutputStream out) throws Exception {
303+
this.writeObject(out);
304+
}
305+
306+
private void writeObject(ObjectOutputStream out) throws IOException {
307+
CustomWriter custom = new CustomWriter(42);
308+
out.writeObject(custom);
309+
out.flush();
310+
}
311+
}
312+
313+
class RandomChild extends Random {
314+
private static final long serialVersionUID = 1;
315+
private int num = 1;
316+
private double doub = 4.5;
317+
318+
RandomChild(int seed) {
319+
super(seed);
320+
}
321+
}
322+
323+
class CustomWriter implements Serializable {
324+
protected RandomChild custom_obj = null;
325+
326+
CustomWriter(int seed) {
327+
custom_obj = new RandomChild(seed);
328+
}
329+
330+
private static final long serialVersionUID = 1;
331+
private static final int CURRENT_SERIAL_VERSION = 0;
332+
private void writeObject(ObjectOutputStream out) throws IOException {
333+
out.writeInt(CURRENT_SERIAL_VERSION);
334+
out.writeObject(custom_obj);
335+
}
336+
}
337+
338+
An here is a sample writing of that kind of object:
339+
340+
.. code-block:: java
341+
342+
ObjectOutputStream oos = new ObjectOutputStream(
343+
new FileOutputStream("custom_objects.ser"));
344+
CustomClass writer = new CustomClass();
345+
writer.start(oos);
346+
oos.flush();
347+
oos.close();
348+
349+
350+
On the Python side, the first step is to define the custom transformers.
351+
They are children of the ``javaobj.v2.transformers.ObjectTransformer`` class.
352+
353+
.. code-block:: python
354+
355+
class BaseTransformer(javaobj.v2.transformers.ObjectTransformer):
356+
"""
357+
Creates a JavaInstance object with custom loading methods for the
358+
classes it can handle
359+
"""
360+
361+
def __init__(self, handled_classes={}):
362+
self.instance = None
363+
self.handled_classes = handled_classes
364+
365+
def create_instance(self, classdesc):
366+
"""
367+
Transforms a parsed Java object into a Python object
368+
369+
:param classdesc: The description of a Java class
370+
:return: The Python form of the object, or the original JavaObject
371+
"""
372+
if classdesc.name in self.handled_classes:
373+
self.instance = self.handled_classes[classdesc.name]()
374+
return self.instance
375+
376+
return None
377+
378+
class RandomChildTransformer(BaseTransformer):
379+
def __init__(self):
380+
super(RandomChildTransformer, self).__init__({'RandomChild': RandomChildInstance})
381+
382+
class CustomWriterTransformer(BaseTransformer):
383+
def __init__(self):
384+
super(CustomWriterTransformer, self).__init__({'CustomWriter': CustomWriterInstance})
385+
386+
class JavaRandomTransformer(BaseTransformer):
387+
def __init__(self):
388+
super(JavaRandomTransformer, self).__init__()
389+
self.name = "java.util.Random"
390+
self.field_names = ['haveNextNextGaussian', 'nextNextGaussian', 'seed']
391+
self.field_types = [
392+
javaobj.v2.beans.FieldType.BOOLEAN,
393+
javaobj.v2.beans.FieldType.DOUBLE,
394+
javaobj.v2.beans.FieldType.LONG
395+
]
396+
397+
def load_custom_writeObject(self, parser, reader, name):
398+
if name == self.name:
399+
fields = []
400+
values = []
401+
for index, value in enumerate(self.field_types):
402+
values.append(parser._read_field_value(value))
403+
fields.append(javaobj.v2.beans.JavaField(value, self.field_names[index]))
404+
405+
class_desc = javaobj.v2.beans.JavaClassDesc(
406+
javaobj.v2.beans.ClassDescType.NORMALCLASS)
407+
class_desc.name = self.name
408+
class_desc.desc_flags = javaobj.v2.beans.ClassDataType.EXTERNAL_CONTENTS
409+
class_desc.fields = fields
410+
class_desc.field_data = values
411+
return class_desc
412+
return None
413+
414+
Second step is defining the representation of the instances, where the real
415+
object loading occurs. Those classes inherit from
416+
``javaobj.v2.beans.JavaInstance``.
417+
418+
.. code-block:: python
419+
420+
# Custom writeObject parsing classes
421+
class CustomWriterInstance(javaobj.v2.beans.JavaInstance):
422+
def __init__(self):
423+
javaobj.v2.beans.JavaInstance.__init__(self)
424+
425+
def load_from_instance(self):
426+
"""
427+
Updates the content of this instance
428+
from its parsed fields and annotations
429+
:return: True on success, False on error
430+
"""
431+
if self.classdesc and self.classdesc in self.annotations:
432+
fields = ['int_not_in_fields'] + self.classdesc.fields_names
433+
raw_data = self.annotations[self.classdesc]
434+
int_not_in_fields = struct.unpack(
435+
'>i', BytesIO(raw_data[0].data).read(4))[0]
436+
custom_obj = raw_data[1]
437+
values = [int_not_in_fields, custom_obj]
438+
self.field_data = dict(zip(fields, values))
439+
return True
440+
441+
return False
442+
443+
444+
class RandomChildInstance(javaobj.v2.beans.JavaInstance):
445+
def load_from_instance(self):
446+
"""
447+
Updates the content of this instance
448+
from its parsed fields and annotations
449+
:return: True on success, False on error
450+
"""
451+
if self.classdesc and self.classdesc in self.field_data:
452+
fields = self.classdesc.fields_names
453+
values = [self.field_data[self.classdesc][self.classdesc.fields[i]] for i in range(len(fields))]
454+
self.field_data = dict(zip(fields, values))
455+
if self.classdesc.super_class and self.classdesc.super_class in self.annotations:
456+
super_class = self.annotations[self.classdesc.super_class][0]
457+
self.annotations = dict(zip(super_class.fields_names, super_class.field_data))
458+
return True
459+
460+
return False
461+
462+
463+
Finally we can use the transformers in the loading process.
464+
Note that even if it is not explicitly given, the ``DefaultObjectTransformer``
465+
will be also be used, as it is added automatically by ``javaobj`` if it is
466+
missing from the given list.
467+
468+
.. code-block:: python
469+
470+
# Load the object using those transformers
471+
transformers = [
472+
CustomWriterTransformer(),
473+
RandomChildTransformer(),
474+
JavaRandomTransformer()
475+
]
476+
pobj = javaobj.loads("custom_objects.ser", *transformers)
477+
478+
# Here we show a field that doesn't belong to the class
479+
print(pobj.field_data["int_not_in_fields"]

0 commit comments

Comments
 (0)