Codebase list libxstream-java / aaea765 xstream-distribution / src / content / converter-tutorial.html
aaea765

Tree @aaea765 (Download .tar.gz)

converter-tutorial.html @aaea765raw · history · blame

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
<html>
<!--
 Copyright (C) 2006 Joe Walnes.
 Copyright (C) 2006, 2007, 2008, 2013 XStream committers.
 All rights reserved.
 
 The software in this package is published under the terms of the BSD
 style license a copy of which has been included with this distribution in
 the LICENSE.txt file.
 
 Created on 10. February 2006 by Mauro Talevi
 -->
  <head>
    <title>Converter Tutorial</title>
  </head>
  <body>

<h1 id="SimpleConverter">Simple Converter</h1>
<h2>Setting up a simple example</h2>

<p>This is the most basic converter... let's start with a simple Person:</p>
<div class="Source Java"><pre>package com.thoughtworks.xstream.examples;

public class Person {

        private String name;

        public String getName() {
                return name;
        }

        public void setName(String name) {
                this.name = name;
        }

}</pre></div><p>So let's create a person and convert it to
XML...</p><div class="Source Java"><pre>package com.thoughtworks.xstream.examples;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

public class PersonTest {

        public static void main(String[] args) {
                Person person = new Person();
                person.setName(&quot;Guilherme&quot;);

                XStream xStream = new XStream(new DomDriver());
                System.out.println(xStream.toXML(person));
        }

}</pre></div><p>This results in a really ugly XML code which contains the full
class name (including
package)...</p><div class="Source Java"><pre>&lt;com.thoughtworks.xstream.examples.Person&gt;
  &lt;name&gt;Guilherme&lt;/name&gt;
&lt;/com.thoughtworks.xstream.examples.Person&gt;</pre></div><p>So we make use
of an 'alias' to change this full class name to something more 'human', for
example
'person'.</p><div class="Source Java"><pre>XStream xStream = new XStream(new DomDriver());
xStream.alias(&quot;person&quot;, Person.class);
System.out.println(xStream.toXML(person));</pre></div><p>And the outcome is
much easier to read (and
smaller):</p><div class="Source Java"><pre>&lt;person&gt;
  &lt;name&gt;Guilherme&lt;/name&gt;
&lt;/person&gt;</pre></div><p>Now that we have configured a simple class to
play with, let's see what XStream converters can do for us...</p>

<h2 id="CreatingPersonConverter">Creating a PersonConverter</h2>
<p>Let's create a simple converter capable
of:</p><ol style="list-style-type: decimal">
<li>telling its capable of converting Person's</li>
<li>translating a Person instance in XML</li>
<li>translate XML into a new Person</li>
</ol><p>We begin creating the PersonConverter class and implementing the
Converter
interface:</p><div class="Source Java"><pre>package com.thoughtworks.xstream.examples;

import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class PersonConverter implements Converter {

        public boolean canConvert(Class clazz) {
                return false;
        }

        public void marshal(Object value, HierarchicalStreamWriter writer,
                        MarshallingContext context) {
        }

        public Object unmarshal(HierarchicalStreamReader reader,
                        UnmarshallingContext context) {
                return null;
        }

}</pre></div><p>Now we tell whoever calls us that we can handle only Person's
(and <b>nothing</b> else, including those classes which extends
Person).</p><div class="Source Java"><pre>public boolean canConvert(Class clazz) {
        return clazz.equals(Person.class);
}</pre></div><p>The second step is usually quite clean, unless you are dealing
with generic converters.</p><p>The marshal method is responsible for
translating an object to XML. It receives three
arguments:</p><ol style="list-style-type: decimal">
<li>the object we are trying to convert</li>
<li>the writer were we should output the data</li>
<li>the current marshalling context</li>
</ol><p>We start casting the object to
person:</p><div class="Source Java"><pre>Person person = (Person) value;</pre></div><p>Now
we can output the data... let's start creating a node called <i>fullname</i>
and adding the person's name to
it:</p><div class="Source Java"><pre>writer.startNode(&quot;fullname&quot;);
writer.setValue(person.getName());
writer.endNode();</pre></div><p>Quite simple
huh?</p><div class="Source Java"><pre>public void marshal(Object value, HierarchicalStreamWriter writer,
                MarshallingContext context) {
        Person person = (Person) value;
        writer.startNode(&quot;fullname&quot;);
        writer.setValue(person.getName());
        writer.endNode();
}</pre></div><p>We could have called start/end node as many times as we would
like (but remember to close everything you open)... and conversion usually
takes place when calling the <i>setValue</i> method.</p><p>And now let's go to
the unmarshal. We use the <i>moveDown</i> and <i>moveUp</i> methods to move
in the tree hierarchy, so we can simply <i>moveDown</i>, read the value and
<i>moveUp</i>.</p><div class="Source Java"><pre>                Person person = new Person();
                reader.moveDown();
                person.setName(reader.getValue());
                reader.moveUp();</pre></div><p>Which gives us the following
converter:</p><div class="Source Java"><pre>package com.thoughtworks.xstream.examples;

import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class PersonConverter implements Converter {

        public boolean canConvert(Class clazz) {
                return clazz.equals(Person.class);
        }

        public void marshal(Object value, HierarchicalStreamWriter writer,
                        MarshallingContext context) {
                Person person = (Person) value;
                writer.startNode(&quot;fullname&quot;);
                writer.setValue(person.getName());
                writer.endNode();
        }

        public Object unmarshal(HierarchicalStreamReader reader,
                        UnmarshallingContext context) {
                Person person = new Person();
                reader.moveDown();
                person.setName(reader.getValue());
                reader.moveUp();
                return person;
        }

}</pre></div><p>Now let's register our converter and see how our application
<i>main</i> method looks
like:</p><div class="Source Java"><pre>package com.thoughtworks.xstream.examples;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

public class PersonTest {

        public static void main(String[] args) {
                Person person = new Person();
                person.setName(&quot;Guilherme&quot;);

                XStream xStream = new XStream(new DomDriver());
                xStream.registerConverter(new PersonConverter());
                xStream.alias(&quot;person&quot;, Person.class);
                System.out.println(xStream.toXML(person));
        }

}</pre></div><p>Did you notice how we registered our converter? It's a simple
call to
<i>registerConverter</i>:</p><div class="Source Java"><pre>xStream.registerConverter(new PersonConverter());</pre></div><p>The
final result
is:</p><div class="Source Java"><pre>&lt;person&gt;
  &lt;fullname&gt;Guilherme&lt;/fullname&gt;
&lt;/person&gt;</pre></div><p>So you might say... that only changed my tree, I
want to convert data!</p><p>Try using an attribute called <i>fullname</i> in
the <i>person</i> tag instead of creating a new child node.</p>

<h2 id="SingleValueConverter">An alternative for types with String representation</h2>

<p>Let's enhance the Person with a String representation, that contains all necessary 
text to recreate the instance:</p>
<div class="Source Java"><pre>package com.thoughtworks.xstream.examples;

public class Person {

        private String name;

        public String getName() {
                return name;
        }

        public void setName(String name) {
                this.name = name;
        }

        public String toString() {
                return getName();
        }
}</pre></div><p>In this case we can simplify our Converter to</p>
<div class="Source Java"><pre>package com.thoughtworks.xstream.examples;

import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;

public class PersonConverter extends AbstractSingleValueConverter {

        public boolean canConvert(Class clazz) {
                return clazz.equals(Person.class);
        }

        public Object fromString(String str) {
                Person person = new Person();
                person.setName(string);
                return person;
        }

}</pre></div><p>But even nicer, our XML is also simplified (using the alias for the 
Person class). Since the String representation is complete, a nested element is not
necessary anymore:</p>
<div class="Source Java"><pre>&lt;person&gt;Guilherme&lt;/person&gt;</pre></div>

<p class=highlight>Note, that in implementation of a SingleValueConverter is required for
attributes, since these objects have to be represented by a single string only.</p>

<h1 id="CustomConverter">Date Converter</h1>
<p>Now that we know how the <i>Converter</i> interface works, let's create a
simple calendar converter which uses the locale to convert the
information.</p><p>Our converter will receive the Locale in its constructor
and we will keep a reference to it in a member
variable:</p><div class="Source Java"><pre>package com.thoughtworks.xstream.examples;

import java.util.Locale;

import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class DateConverter implements Converter {

        private Locale locale;

        public DateConverter(Locale locale) {
                super();
                this.locale = locale;
        }

        public boolean canConvert(Class clazz) {
                return false;
        }

        public void marshal(Object value, HierarchicalStreamWriter writer,
                        MarshallingContext context) {
        }

        public Object unmarshal(HierarchicalStreamReader reader,
                        UnmarshallingContext context) {
                return null;
        }

}</pre></div><p>Now let's convert anything which extends <i>Calendar</i>:
means if instances of class <i>clazz</i> can be assigned to the
<i>Calendar</i> class, they extends the abstract class
<i>Calendar</i>:</p><div class="Source Java"><pre>public boolean canConvert(Class clazz) {
        return Calendar.class.isAssignableFrom(clazz);
}</pre></div><p>Let's go for converting a <i>Calendar</i> in a localized
string... we first cast the object to <i>Calendar</i>, extract its <i>Date</i>
and then use a <i>DateFormat</i> factory method to get a date converter to our
localized
string.</p><div class="Source Java"><pre>public void marshal(Object value, HierarchicalStreamWriter writer,
                MarshallingContext context) {

        Calendar calendar = (Calendar) value;

        // grabs the date
        Date date = calendar.getTime();

        // grabs the formatter
        DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
                        this.locale);

        // formats and sets the value
        writer.setValue(formatter.format(date));

}</pre></div><p>And the other way around... in order to unmarshall, we create
a <i>GregorianCalendar</i>, retrieves the localized <i>DateFormat</i>
instance, parses the string into a <i>Date</i> and puts this date in the
original
<i>GregorianCalendar</i>:</p><div class="Source Java"><pre>public Object unmarshal(HierarchicalStreamReader reader,
                UnmarshallingContext context) {

        // creates the calendar
        GregorianCalendar calendar = new GregorianCalendar();

        // grabs the converter
        DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
                        this.locale);

        // parses the string and sets the time
        try {
                calendar.setTime(formatter.parse(reader.getValue()));
        } catch (ParseException e) {
                throw new ConversionException(e.getMessage(), e);
        }

        // returns the new object
        return calendar;

}</pre></div><p>Note 1: remember that some <i>DateFormat</i> implementations
are not thread-safe, therefore don't put your formatter as a member of your
converter.</p><p>Note 2: this implementation <b>will</b> convert other types
of Calendar's to GregorianCalendar after save/load. If this is not what you
want, change your <i>canConvert</i> method to return <i>true</i> only if
<i>class</i> equals <i>GregorianCalendar</i>.</p><p>So we get the following
converter:</p><div class="Source Java"><pre>package com.thoughtworks.xstream.examples;

import java.text.DateFormat;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;

import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class DateConverter implements Converter {

        private Locale locale;

        public DateConverter(Locale locale) {
                super();
                this.locale = locale;
        }

        public boolean canConvert(Class clazz) {
                return Calendar.class.isAssignableFrom(clazz);
        }

        public void marshal(Object value, HierarchicalStreamWriter writer,
                        MarshallingContext context) {
                Calendar calendar = (Calendar) value;
                Date date = calendar.getTime();
                DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
                                this.locale);
                writer.setValue(formatter.format(date));
        }

        public Object unmarshal(HierarchicalStreamReader reader,
                        UnmarshallingContext context) {
                GregorianCalendar calendar = new GregorianCalendar();
                DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL,
                                this.locale);
                try {
                        calendar.setTime(formatter.parse(reader.getValue()));
                } catch (ParseException e) {
                        throw new ConversionException(e.getMessage(), e);
                }
                return calendar;
        }

}</pre></div><p>And let's try it out. We create a <i>DateTest</i> class with a
<i>main</i> method:</p><ol style="list-style-type: decimal">
<li>creates a calendar (current date)</li>
<li>creates the XStream object</li>
<li>registers the converter with a Brazilian Portuguese locale</li>
<li>translates the object in XML</li>
</ol>
<p>Well, we already know how to do all those steps... so let's go:</p>
<div class="Source Java"><pre>package com.thoughtworks.xstream.examples;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

public class DateTest {

        public static void main(String[] args) {

                // grabs the current date from the virtual machine
                Calendar calendar = new GregorianCalendar();

                // creates the xstream
                XStream xStream = new XStream(new DomDriver());

                // brazilian portuguese locale
                xStream.registerConverter(new DateConverter(new Locale(&quot;pt&quot;, &quot;br&quot;)));

                // prints the result
                System.out.println(xStream.toXML(calendar));

        }

}</pre></div><p>The result? Well... it depends, but it will be something
like:</p><div class="Source Java"><pre>&lt;gregorian-calendar&gt;Sexta-feira, 10 de Fevereiro de 2006&lt;/gregorian-calendar&gt;</pre></div><p>Note:
we did not put any alias as <i>gregorian-calendar</i> is the default alias for
<i>GregorianCalendar</i>.</p><p>And now let's try to unmarshal the result
shown
above:</p><div class="Source Java"><pre>// loads the calendar from the string
Calendar loaded = (Calendar) xStream
                .fromXML(&quot;&lt;gregorian-calendar&gt;Sexta-feira, 10 de Fevereiro de 2006&lt;/gregorian-calendar&gt;&quot;);</pre></div><p>And
print it using the system locale, short date
format:</p><div class="Source Java"><pre>// prints using the system defined locale
System.out.println(DateFormat.getDateInstance(DateFormat.SHORT).format(
                loaded.getTime()));</pre></div><p>The result might be
something like (if your system locale is American
English):</p><div class="Source Java"><pre>2/10/06</pre></div>

<h1 id="ComplexConverter">Complex Converter</h1>
<h2>Setting up another example</h2>

<p>We already defined some classes, so let them glue together:</p>
<div class="Source Java"><pre>package com.thoughtworks.xstream.examples;

public class Birthday {

        private Person person;
        private Calendar date;
        private char gender;

        public Person getPerson() {
                return person;
        }

        public void setPerson(Person person) {
                this.person = person;
        }

        public Calendar getDate() {
                return date;
        }

        public void setDate(Calendar date) {
                this.date = date;
        }
        
        public char getGender() {
                return gender;
        }

        public void setGenderMale() {
                this.gender = 'm';
        }

        public void setGenderFemale() {
                this.gender = 'f';
        }

}</pre></div><p>While XStream is capable of converting this class without any problem, we write our own custom converter
just for demonstration. This time we want to reuse our already written converters for the Person and the Calendar and add an
own attribute for the gender. The <code>canConvert</code> method is plain simple. We convert no derived classes this time,
since they might have additional fields. But we reuse the converters registered in XStream for our member fields and handle
<code>null</code> values:
</p><div class="Source Java"><pre>package com.thoughtworks.xstream.examples;

import java.util.Calendar;

import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class BirthdayConverter implements Converter {

        public boolean canConvert(Class clazz) {
                return Birthday.class == clazz;
        }

        public void marshal(Object value, HierarchicalStreamWriter writer,
                        MarshallingContext context) {
                Birthday birthday = (Birthday)value;
                if (birthday.getGender() != '\0') {
                        writer.addAttribute("gender", Character.toString(birthday.getGender()));
                }
                if (birthday.getPerson() != null) {
                        writer.startNode("person");
                        context.convertAnother(birthday.getPerson());
                        writer.endNode();
                }
                if (birthday.getDate() != null) {
                        writer.startNode("birth");
                        context.convertAnother(birthday.getDate());
                        writer.endNode();
                }
        }

        public Object unmarshal(HierarchicalStreamReader reader,
                        UnmarshallingContext context) {
                Birthday birthday = new Birthday();
                String gender = reader.getAttribute("gender");
                if (gender != null) {
                        if (gender.length() &gt; 0) {              
                                if (gender.char(0) == 'f') {
                                        birthday.setGenderFemale();
                                } else if (gender.char(0) == 'm') {
                                        birthday.setFemale();
                                } else {
                                        throw new ConversionException("Invalid gender value: " + gender);
                                }
                        } else {
                                throw new ConversionException("Empty string is invalid gender value");
                        }
                }
                while (reader.hasMoreChildren()) {
                        reader.moveDown();
                        if ("person".equals(reader.getNodeName())) {
                                Person person = (Person)context.convertAnother(birthday, Person.class);
                                birthday.setPerson(person);
                        } else if ("birth".equals(reader.getNodeName())) {
                                Calendar date = (Calendar)context.convertAnother(birthday, Calendar.class);
                                birthday.setDate(date);
                        }
                        reader.moveUp();
                }
                return birthday;
        }

}</pre></div><p>The unmarshal method ensures the valid value for the gender by throwing a
ConversionException for invalid entries.</p>

<p class=highlight>Note, that attributes will always have to be written and read first. You work on a stream and
accessing the value of a tag or its members will close the surrounding tag (that is still active when the method is
called).</p>

<p>If the implementation of <code>Birthday</code> ensures, that none of its fields
could hold a <code>null</code> value and gender contains a valid value, then we could drop the 
<code>null</code> condition in the <code>marshal</code> method and in <code>unmarshal</code>
we could omit the loop as well as the comparison of the tag names:</p><div class="Source Java"><pre>package com.thoughtworks.xstream.examples;

import java.util.Calendar;

import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class BirthdayConverter implements Converter {

        public boolean canConvert(Class clazz) {
                return Birthday.class == clazz;
        }

        public void marshal(Object value, HierarchicalStreamWriter writer,
                        MarshallingContext context) {
                Birthday birthday = (Birthday)value;
                writer.addAttribute("gender", Character.toString(birthday.getGender()));
                writer.startNode("person");
                context.convertAnother(birthday.getPerson());
                writer.endNode();
                writer.startNode("birth");
                context.convertAnother(birthday.getDate());
                writer.endNode();
        }

        public Object unmarshal(HierarchicalStreamReader reader,
                        UnmarshallingContext context) {
                Birthday birthday = new Birthday();
                if (reader.getAttribute("gender").charAt(0) == 'm') {
                        birthday.setGenderMale();
                } else {
                        birthday.setGenderFemale();
                }
                reader.moveDown();
                Person person = (Person)context.convertAnother(birthday, Person.class);
                birthday.setPerson(person);
                reader.moveUp();
                reader.moveDown();
                Calendar date = (Calendar)context.convertAnother(birthday, Calendar.class);
                birthday.setDate(date);
                reader.moveUp();
                return birthday;
        }

}</pre></div>
 </body>
</html>