Re: XMLEncoder problem

M

Mark Space

Ken said:
I seem to have a misunderstanding of how XMLEncoder works and I'm trying
to resolve it. I thought that XMLEncoder, by default, went through a
Java beans public properties and called get methods to save them, and
then XMLDecoder would call the appropriate setters to set them again, but

It does. You have two problems with your example.

First, you have a getWords, but no setWords. So even given your
understanding it should be obvious that this can't work. XMLDecoder
doesn't' recognize "add" as a substitute for "set".

The second problem is a little more subtle. "getWords" returns a list,
and List is also not a bean. It has a get() method, but that method
needs a parameter, and XMLEncoder can't know what to supply. And
there's still no "set" method for List, so XMLDecoder cant' do a thing
with it.

You'll have to supply getters and setters that return and take
primitives, or arrays of primitives. Or beans, but Collections aren't
beans.

My advice: use the Serialization API. Adding all these methods needed
for XMLEncoder/Decoder will really warp the encapsulation of your
classes. Serialization is much, much easier to work with. And I think
most Collection classes are serializable to boot.

If you don't want to use serialization, you should probably break down
and modify your use of XMLEncoder/Decoder so the API on your classes.
Provide a factor method or something similar to generate your nicely
encapsulated classes from a stream of beans, then use XMLDecoder to get
the beans. You'll also need to make a factory method to make beans from
your main classes, and then use XMLEncoder on that. I think this will
be much cleaner in the long run for you.
 
S

Stefan Ram

Mark Space said:
You'll have to supply getters and setters that return and take
primitives, or arrays of primitives. Or beans, but Collections
aren't beans.

But somehow, the following prints:

*** Main ***

alpha
1.0

class Main
{ public static void main( final String[] args ) throws java.lang.Throwable
{
final ExampleBean a = new ExampleBean();
final java.util.List<java.lang.Object> list =
new java.util.ArrayList<java.lang.Object>();
list.add( "alpha" );
list.add( new Double( 1 ) );
a.setProperty( list );
final java.beans.XMLEncoder output = new java.beans.XMLEncoder
( new java.io.BufferedOutputStream
( new java.io.FileOutputStream( new java.io.File( "tmp.xml" ))));
try{ output.writeObject( a ); }
finally{ output.close();

final java.io.FileInputStream fileInputStream =
new java.io.FileInputStream( new java.io.File( "tmp.xml" ));
final ExampleBean b =
( ExampleBean )new java.beans.XMLDecoder( fileInputStream ).readObject();

java.lang.System.out.println( "*** Main ***\n" );
java.lang.System.out.println( b.getProperty().get( 0 ));
java.lang.System.out.println( b.getProperty().get( 1 )); }}}

public class ExampleBean
{ private java.util.List<java.lang.Object> data;
public ExampleBean(){ data = null; }
public ExampleBean( final java.util.List<java.lang.Object> value )
{ this.data = value; }
public java.util.List<java.lang.Object> getProperty(){ return data; }
public void setProperty( final java.util.List<java.lang.Object> value )
{ this.data = value; }}
 
T

Thomas Kellerer

Mark Space wrote on 21.12.2008 00:35:
The second problem is a little more subtle. "getWords" returns a list,
and List is also not a bean. It has a get() method, but that method
needs a parameter, and XMLEncoder can't know what to supply. And
there's still no "set" method for List, so XMLDecoder cant' do a thing
with it.
Interesting enough, Collections themselves can be saved:

List<String> words = new ArrayList<String>();
words.add("one");
words.add("two");

XMLEncoder e = new XMLEncoder(new FileOutputStream("test.xml"));
e.writeObject(words);
e.close();

produces the following output:

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.5.0_12" class="java.beans.XMLDecoder">
<object class="java.util.ArrayList">
<void method="add">
<string>one</string>
</void>
<void method="add">
<string>two</string>
</void>
</object>
</java>

So if the object that is saved is a Collection it does work. But it does not
seem to work for attributes that are Collections. Which makes the behaviour that
a collection as a "property" is not saved a bit hard to understand.

Thomas
 
S

Stefan Ram

java.lang.System.out.println( "*** Main ***\n" );

This has a minor stylistic issue - can you spot it?

S

P

O

I

L

E

R



S

P

O

I

L

E

R



S

P

O

I

L

E

R



S

P

O

I

L

E

R



S

P

O

I

L

E

R



S

P

O

I

L

E

R



S

P

O

I

L

E

R



S

P

O

I

L

E

R



S

P

O

I

L

E

R



S

P

O

I

L

E

R



S

P

O

I

L

E

R



It is equivalent to

java.lang.System.out.printf( "*** Main ***\n%n" );

But I deem it bad style to mix »\n« with »%n« with
no good reason why first »\n« and then »%n« is used.

Either one wants to use »\n« as a line separator
or »%n«, so one should use »\n\n« or »%n%n«,
respectively.
 
T

Thomas Kellerer

Mark Space wrote on 21.12.2008 00:35:
First, you have a getWords, but no setWords.
Btw: I can see a setWords() in the original example.

The second problem is a little more subtle. "getWords" returns a list,
and List is also not a bean. It has a get() method, but that method
needs a parameter, and XMLEncoder can't know what to supply. And
there's still no "set" method for List, so XMLDecoder cant' do a thing
with it.

I played around a bit more. XMLEncode _can_ save collections even if they are
"properties" of a class. The problem is, that XMLEncoder does not save
properties that are "unititialized", which means properties that have the same
value as a property of the classes instance after calling the default
constructor. So a boolean property that is false (and is not set to true in the
default constructor) will not be written to the output.

If the OP changes the code to not initialize the wordList variable in the
constructor, but initialize it lazily in addWord, then XMLEncoder should save
the list as well:

public WordList() {
words = new HashSet<String>();
random = new Random();
}

public void addWord(String word, String definition) {
if (wordList == null) wordList = new ArrayList<Word>();
if (!words.add(word.toLowerCase())) {
throw new IllegalArgumentException();
}
wordList.add(new Word(word, definition));
}

Thus the XMLEncode will notice that the property "words" (defined by
set/getWords) is actually different compared to the state after the default
constructor and writes the List to the output.

Thomas
 
A

Arne Vajhøj

Ken said:
Mark Space wrote on 21.12.2008 00:35: [Snip]
If the OP changes the code to not initialize the wordList variable in
the constructor, but initialize it lazily in addWord, then XMLEncoder
should save the list as well:
[Snip]

OK, I tried leaving both variables, words and wordList, as null after the
constructor completes and setting them only when a word is added or when
setWords is called. This had no affect on the program at all.

The thing is, setWords is not being called. If I put a breakpoint in
setWords, it is never hit. Somehow XMLDecode is reconstructing the
object without ever calling setWords(). The only way I can see this
happening is if it is cheating and using reflection to set private member
variables. So, I'm really confused about how XMLDecode works. Any
insight as to how this is possible, or what XMLDecode could be doing
under the hood would be greatly appreciated. Somehow it is reading back
wordList, without setting words.

The Java source code comes with the JDK, so you could look
at XMLDecode and see what it does.

Arne
 
M

Mark Space

Ken said:
The code has both a getWords and a setWords. The method addWord is
unrelated.

The rest of your discussion is commented on further in down the thread.
Thanks for your help.


Yup, it does. Sorry about that, I missed it. And for some reason I had
trouble with running collections through XMLEncoder when I tried it, so
I switched to arrays of primitives and it worked. Oh well, I guess I
get to learn something too.
 
M

Mark Space

Ken said:
It is actually the variable, words, that doesn't seem to be being saved.

Have I gone blind again? I don't see any setters or getters for words
at all, just word list. Even the add() method adds a world to wordList,
not words. Nothing that I see even touches "words".
 
M

Mark Space

Ken said:
OK, I tried leaving both variables, words and wordList, as null after the
constructor completes and setting them only when a word is added or when
setWords is called. This had no affect on the program at all.

The following does work for me, you might have some sort of logic error
in your current program.


*******<XmlTest.java>

package xmlencdec;

import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


public class XmlTest {

private List<Words> wordsDefs;
private Set<String> words;

public XmlTest()
{
wordsDefs = new ArrayList<Words>();
words = new HashSet<String>();
}

public Set<String> getWords() {
HashSet<String> temp = new HashSet<String>();
temp.addAll( words );
return temp;
}

public void setWords( Set<String> s ) {
words = new HashSet();
words.addAll( s );
}

public List<Words> getWordsDefs()
{
return wordsDefs;
}

public void setWordsDefs( List<Words> wordsDefs )
{
this.wordsDefs = wordsDefs;
}


public void add( String w, String d )
{
wordsDefs.add( new Words( w, d ) );
words.add( w );
}

@Override
public String toString() {
return words.toString() + wordsDefs.toString();
}

/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here

ArrayList<String> al = new ArrayList<String>();
al.add( "one fish" );
al.add( "two fish" );
System.out.println( testEncoderDecoder( al ) );

XmlTest xt = new XmlTest();
xt.add( "one fish", "red fish" );
xt.add( "two fish", "blue fish" );
System.out.println( testEncoderDecoder( xt ) );
}

private static Object testEncoderDecoder( Object o )
{
ByteArrayOutputStream ao = new ByteArrayOutputStream( 16*1024 );

XMLEncoder xe = new XMLEncoder( ao );

xe.writeObject( o );
xe.close();

byte[] bytes = ao.toByteArray();
ByteArrayInputStream ai = new ByteArrayInputStream( bytes );

System.out.println( "Object: " + o.getClass().getName() );

System.out.println( "XML:" );
BufferedReader br = new BufferedReader( new InputStreamReader(
ai ));
String line;
try {
while( (line = br.readLine()) != null ) {
System.out.println( line );
}
}
catch( IOException ex ) {
ex.printStackTrace();
throw new WrappedException( ex );
}

XMLDecoder xd = new XMLDecoder( new ByteArrayInputStream( bytes
) );
Object retVal = xd.readObject();
xd.close();
return retVal;
}


static class WrappedException extends RuntimeException {

public WrappedException( Throwable cause )
{
super( cause );
}

}
}

******</XmlTest.java>

******<Words.java>

package xmlencdec;


public class Words {
private String word;
private String definition;

public Words()
{
}

public Words( String word, String definition )
{
this.word = word;
this.definition = definition;
}

public String getDefinition()
{
return definition;
}

public void setDefinition( String definition )
{
this.definition = definition;
}

public String getWord()
{
return word;
}

public void setWord( String word )
{
this.word = word;
}

@Override
public String toString() {
return word+":"+definition;
}
}
*******</Words.java>
 
M

Mike Schilling

Ken said:
Mark Space wrote on 21.12.2008 00:35: [Snip]
If the OP changes the code to not initialize the wordList variable
in
the constructor, but initialize it lazily in addWord, then
XMLEncoder
should save the list as well:
[Snip]

OK, I tried leaving both variables, words and wordList, as null
after
the constructor completes and setting them only when a word is added
or when setWords is called. This had no affect on the program at
all.

The thing is, setWords is not being called. If I put a breakpoint
in
setWords, it is never hit. Somehow XMLDecode is reconstructing the
object without ever calling setWords(). The only way I can see this
happening is if it is cheating and using reflection to set private
member variables. So, I'm really confused about how XMLDecode
works.

Here's how it works, to my understanding. First, XMLEncoder creates a
default instance, using the default constructor. Now it does the
following loop:

For each bean property
If the default and actual instances, have different values for
that property
Then
output the actual instance's value as XML
End if
End for

XmlDecoder merely creates a default instance and sets its properties
according to the XML document. No magic, no access to private fields.
In fact, one opf the advantages of using XMLEncoder is that you can
totally reimplement the class so long as you keep the same bean
properties.

So if I were you I'd start by looking at the XML file. If you don't
see a represeantion of "words", the encoding is failing. If you do,
but "words" has the wrong value in the decoded instance, the decoding
is failing. Both encoding and decoding just call your bean methods,
so setting breakpoints in them (or just adding print statements)
should help you debug this.
 
S

Stefan Ram

Ken T. said:
I've set a breakpoint in setWords () and it doesn't ever hit
it. I don't see how this is consistent with the way you say it
should be working.

If everything else fails, there is one last ressort:
Prepare and post an SSCCE that will reproduce the effect.
 
A

Arved Sandstrom

It is actually the variable, words, that doesn't seem to be being saved.
I don't understand how this works really, but I'll try making both
variables initialize lazily and see if that changes anything.

Except that when I set up a project with your WordList class (plus a
class for Word), plus a main class that does this with your stuff:

WordList wl = new WordList();
Word word1 = new Word("esoteric", "rare");
Word word2 = new Word("etymology", "history of words");
ArrayList<Word> otherWords = new ArrayList<Word>();
otherWords.add(word1);
otherWords.add(word2);
wl.setWords(otherWords);

before calling XMLEncoder and XMLDecoder, it _is_ "words" that gets
saved, as seen by the below:

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.6.0_10" class="java.beans.XMLDecoder">
<object class="org.ahs.beans.WordList">
<void property="words">
<void method="add">
<object class="org.ahs.beans.Word">
<void property="definition">
<string>rare</string>
</void>
<void property="text">
<string>esoteric</string>
</void>
</object>
</void>
<void method="add">
<object class="org.ahs.beans.Word">
<void property="definition">
<string>history of words</string>
</void>
<void property="text">
<string>etymology</string>
</void>
</object>
</void>
</void>
</object>
</java>

The decoder is also reading back correctly, because I call getWords() and
print out the results.

AHS
 
A

Arved Sandstrom

If the method setWords() is called it initializes both wordList and
words. The only time words is really used is when a word is added to
the list. If it already exists, it is in words, then it isn't added and
an exception is thrown.

So when I use XMLDecoder I get all the words in wordList, but I'm still
able to add a word that already exists in wordList since words is not
updated. Somehow XMLDecoder is initializing wordList without calling
the setter. At least that is the best guess I have.

Based on the XML I saw, and printouts, I believe that the decoder is
creating an empty wordList, then calling add(Word word) on it for each
word. That's why you never see the setter called.

AHS
 
S

Stefan Ram

Ken T. said:
WordList wordList = new WordList();
wordList.addWord("red", "a color");
wordList.addWord("blue", "another color");
wordList.addWord("green", "a last color");
wordList.addWord("red", "a color already there");
XMLEncoder encoder = new XMLEncoder(new FileOutputStream(tmp));

There is no »setWords()« call after »new WordList()«, so
»wordList« still has its value from the parameterless
constructor.

With the code supplied, I get:

An expected exception was thrown
An expected exception was not thrown

. The output

An expected exception was thrown
An expected exception was thrown

is obtained after two changes:

Set »wordList« to »null« in the constructor:

public WordList() {
wordList = null;
words = new HashSet<String>();
random = new Random();
}

and use »setWords« once in »main«: The two original lines from »main«:

WordList wordList = new WordList();
wordList.addWord("red", "a color");

become the three lines:

WordList wordList = new WordList();
wordList.setWords( new ArrayList<Word>() );
wordList.addWord("red", "a color");

. This behavior is consistent with the documentation of XMLDecoder.

~~

If you do not like XMLEncoder, you might have a look at

http://xstream.codehaus.org/

~~

I also have written my own custom serializer in

http://www.purl.org/stefan_ram/pub/ram-jar

It can be called via

new de.dclj.ram.notation.junobjects.Junobjects().dump( wordList )

which will return the string that makes up the rest of this
post - but I am afraid my de-serializer is not yet finished ...

< &objectmap
object =
< &WordList
random =
< &java.util.Random zz2 >
wordList =
< &java.util.ArrayList zz1 >
words =
< &java.util.HashSet zz0 >>
zz0 =
< &java.util.HashSet
map =
< &java.util.HashMap zz4 >>
zz1 =
< &java.util.ArrayList
elementData =
< &[java.lang.Object[]]
< &Word zz5 >
< &Word zz6 >
< &Word zz7 >
< >
< >
< >
< >
< >
< >
< >>
size =
< &int 3 >>
zz10 =
< &java.lang.String
count =
< &int 5 >
hash =
< &int 98619139 >
offset =
< &int 0 >
value =
< &[char[]] g r e e n >>
zz11 =
< &java.lang.String
count =
< &int 12 >
hash =
< &int 0 >
offset =
< &int 0 >
value =
< &[char[]] a [ ] l a s t [ ] c o l o r >>
zz12 =
< &java.util.HashMap.Entry
hash =
< &int 106611 >
key =
< &java.lang.String zz15 >
next =
< >
value =
< &java.lang.Object zz16 >>
zz13 =
< &java.util.HashMap.Entry
hash =
< &int 2919925 >
key =
< &java.lang.String zz8 >
next =
< >
value =
< &java.lang.Object zz16 >>
zz14 =
< &java.util.HashMap.Entry
hash =
< &int 95770982 >
key =
< &java.lang.String zz10 >
next =
< >
value =
< &java.lang.Object zz16 >>
zz15 =
< &java.lang.String
count =
< &int 3 >
hash =
< &int 112785 >
offset =
< &int 0 >
value =
< &[char[]] r e d >>
zz16 =
< &java.lang.Object >
zz17 =
< &java.lang.String
count =
< &int 7 >
hash =
< &int 0 >
offset =
< &int 0 >
value =
< &[char[]] a [ ] c o l o r >>
zz2 =
< &java.util.Random
haveNextNextGaussian =
< &boolean false >
nextNextGaussian =
< &double 0.0 >
seed =
< &java.util.concurrent.atomic.AtomicLong zz3 >>
zz3 =
< &java.util.concurrent.atomic.AtomicLong
value =
< &long 4744465127030 >>
zz4 =
< &java.util.HashMap
entrySet =
< >
loadFactor =
< &float 0.75 >
modCount =
< &int 3 >
size =
< &int 3 >
table =
< &[java.util.HashMap.Entry[]]
< >
< >
< >
< &java.util.HashMap.Entry zz12 >
< >
< &java.util.HashMap.Entry zz13 >
< &java.util.HashMap.Entry zz14 >
< >
< >
< >
< >
< >
< >
< >
< >
< >>
threshold =
< &int 12 >>
zz5 =
< &Word
definition =
< &java.lang.String zz17 >
word =
< &java.lang.String zz15 >>
zz6 =
< &Word
definition =
< &java.lang.String zz9 >
word =
< &java.lang.String zz8 >>
zz7 =
< &Word
definition =
< &java.lang.String zz11 >
word =
< &java.lang.String zz10 >>
zz8 =
< &java.lang.String
count =
< &int 4 >
hash =
< &int 3027034 >
offset =
< &int 0 >
value =
< &[char[]] b l u e >>
zz9 =
< &java.lang.String
count =
< &int 13 >
hash =
< &int 0 >
offset =
< &int 0 >
value =
< &[char[]] a n o t h e r [ ] c o l o r >>>
 
A

Arved Sandstrom

Ken T. said:
On Sun, 21 Dec 2008 06:27:37 +0000, Ken T. wrote:
[ SNIP ]
So when I use XMLDecoder I get all the words in wordList, but I'm still
able to add a word that already exists in wordList since words is not
updated. Somehow XMLDecoder is initializing wordList without calling
the setter. At least that is the best guess I have.

Based on the XML I saw, and printouts, I believe that the decoder is
creating an empty wordList, then calling add(Word word) on it for each
word. That's why you never see the setter called.

AHS

It doesn't hit a breakpoint there either. It must be accessing the
private member variables. I'll put together an sample program that shows
exactly what I'm talking about.

I meant the method add(E e) in ArrayList. If the setters are not being
called, I see no other mechanism than that.

AHS
 
S

Stefan Ram

Ken T. said:
It doesn't hit a breakpoint there either. It must be accessing the
private member variables. I'll put together an sample program that shows
exactly what I'm talking about.

Here is what is happening in your original version:

The XMLEncoder detects that the reference
»wordList.getWords()« was not changed after the construction,
but its contents was changed. So it puts out the new contents
(three entries).

The XMLDecoder then uses »getWords()« to get a reference »r«
to »wordList.wordList«, and then does »r.add(...)« three
times.

You can confirm this with your debugger: A break point
in »getWords()« will be hit three times after the call
»readObject()«.

So, it does not access the private member variable directly,
but via »getWords()«.
 
A

Arved Sandstrom

Ken T. said:
On Sun, 21 Dec 2008 00:47:20 +0000, Ken T. wrote:
[snip]
The decoder is also reading back correctly, because I call getWords()
and print out the results.

You and I are talking about two different things. There is the property
"words" and there is the private member variable, words.

I did understand that, even though it's confusing - for the sake of
maintenance programmers everywhere I'd encourage you to map getter/setter
names to the field names. :)
The private member variable words is not getting set since if you try to
add a word that is already in the list, you can add it. The property
words does appear to be set since getWords returns the value in wordList,
not the value in words.

I agree that the private variable "words" is not being set. It's also clear
that "wordList" is being loaded up, since as you point out, that's what
"getWords()" returns. My conclusion is, as I've stated, that a new empty
WordList is being created (a println in the right place tells you that this
in fact happens), and that the method add(E e) is being called on the
ArrayList to add each Word. In other words, the setter is not being called,
and addWord is also not being called. I've seen nothing in the XMLDecoder
Javadoc to suggest that the setter needs to be called.

You may reasonably point out, how did XMLEncoder and XMLEncoder figure out
what to do? Well, they have one public getter in your class - getWords,
which returns an ArrayList. They also have a constructor. If XMLDecoder
calls the WordList constructor, which it certainly appears to do, then we
have non-null empty "words" and "wordList". If it then calls getWords()
it'll have an ArrayList to start adding Words to with add(). This results in
what we see. At no time does XMLEncoder/XMLDecoder know that it's dealing
with private member variable "wordList"...it's just calling this property
"words".

I don't know for a fact that this is what is going on, but it's my guess.

AHS
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,777
Messages
2,569,604
Members
45,229
Latest member
GloryAngul

Latest Threads

Top