@@ -90,6 +90,15 @@ Bytes arrays
9090As of version 0.2.3, bytes arrays are loaded as a ``bytes `` object instead of
9191an 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
94103Features
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