gwtproject / gwt

GWT Open Source Project
http://www.gwtproject.org
1.52k stars 377 forks source link

NullPointerException trying to serialize with AutoBeanFactory an object which contains a List #6903

Open dankurka opened 9 years ago

dankurka commented 9 years ago

Originally reported on Google Code with ID 6904

Found in GWT Release (e.g. 2.4.0, 2.5.0 RC):

GWT 2.3.0, GWT 2.4.0

Encountered on OS / Browser (e.g. WinXP, IE8-9, FF7):

Windows 7, Ubuntu 10.04, Ubuntu 11.10

Detailed description (please be as specific as possible):

If using an AutoBeanFactory you try to serialize an Object which contains a list of
beans you get a NullPointerException

Shortest code snippet which demonstrates issue (please indicate where
actual result differs from expected result):

Create those beans:

public interface IBean {String getHello();}

public interface IBeanList {List<IBean> getList();}

public class Bean implements IBean {
    private final String hello;
    public Bean(String hello){this.hello = hello;}
    public String getHello() {return hello;}    
}

public class BeanList implements IBeanList {
    private List<IBean> list = new ArrayList<IBean>();
    public List<IBean> getList() {return list;}
}

public interface BeanFactory extends AutoBeanFactory {
    AutoBean<IBean> bean();
    AutoBean<IBean> bean(IBean bean);   
    AutoBean<IBeanList> beanList(); 
    AutoBean<IBeanList> beanList(IBeanList list);   
}

The code that produce the error:

    BeanFactory beanFactory = GWT.create(BeanFactory.class);

    IBeanList beanList = new BeanList();
    beanList.getList().add( new Bean("hello1") );
    beanList.getList().add( new Bean("hello2") );
    beanList.getList().add( new Bean("hello3") );

    AutoBean<IBeanList> autoBean = beanFactory.create(IBeanList.class, beanList);
    //Here you get the NullPointerException:
    Splittable encodeResult = AutoBeanCodex.encode(autoBean);
    String beanListJson = encodeResult.getPayload();

The stacktrace of the exception:
java.lang.NullPointerException
    at com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.doEncode(AutoBeanCodexImpl.java:558)
    at com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl$ObjectCoder.encode(AutoBeanCodexImpl.java:321)
    at com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl$CollectionCoder.encode(AutoBeanCodexImpl.java:163)
    at com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl$PropertyGetter.encodeProperty(AutoBeanCodexImpl.java:413)
    at com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl$PropertyGetter.visitReferenceProperty(AutoBeanCodexImpl.java:389)
    at com.google.web.bindery.autobean.shared.AutoBeanVisitor.visitCollectionProperty(AutoBeanVisitor.java:229)
    at com.test.gwt.client.IBeanListAutoBean.traverseProperties(IBeanListAutoBean.java:53)
    at com.google.web.bindery.autobean.shared.impl.AbstractAutoBean.traverse(AbstractAutoBean.java:166)
    at com.google.web.bindery.autobean.shared.impl.AbstractAutoBean.accept(AbstractAutoBean.java:101)
    at com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.doEncode(AutoBeanCodexImpl.java:558)
    at com.google.web.bindery.autobean.shared.AutoBeanCodex.encode(AutoBeanCodex.java:83)

Workaround if you have one:

We found a (really dirty) workaround for this, changing the code above with the following
it works:

    BeanFactory beanFactory = GWT.create(BeanFactory.class);

    IBeanList beanList = new BeanList();
    beanList.getList().add( new Bean("hello1") );
    beanList.getList().add( new Bean("hello2") );
    beanList.getList().add( new Bean("hello3") );

    // If you loop through the list and encode every object in the list, then the encoding
works.
    // This is absolutely strange because in this loop I completely ignore the result
of the object
    // returned by the AutoBeanCodex.encode(autoBean) call.
    for (IBean bean : beanList.getList()) {
        AutoBean<IBean> autoBean = beanFactory.create(IBean.class, bean);
        // I ignore the object returned by this call.
        AutoBeanCodex.encode(autoBean);
    }

    AutoBean<IBeanList> autoBean = beanFactory.create(IBeanList.class, beanList);
    // Now it works!
    Splittable encodeResult = AutoBeanCodex.encode(autoBean);
    String beanListJson = encodeResult.getPayload();

Reported by ufoscout@yahoo.it on 2011-10-18 09:14:45

dankurka commented 9 years ago

Reported by rdayal@google.com on 2011-11-04 20:07:23

dankurka commented 9 years ago
This happens with my setup as well - deeper in encode the object you're encoding is
expected to have an AutoBean already made for it, but somehow in `create` the nested
objects are not autobeaned.

I'm using 2.3, _before_ the move to web.bindery

Reported by rileylark on 2011-11-16 21:04:13

dankurka commented 9 years ago
Another work around detailed here: 
https://groups.google.com/forum/#!msg/google-web-toolkit/nvIotNHy-Io/YcbECPWd-v4J

instead of:
 beanList.getList().add( new Bean("hello1") );
use: 
 beanList.getList().add( beanFactory.bean(new Bean("hello1")).as() );

Reported by aidanok on 2012-04-21 18:11:56

dankurka commented 9 years ago
Hi,

we've ran into the same problem and I did some debugging and thought I'd share. In
the generated classes for the 'usual' objects (non lists it seems) there is a chunk
of code that looks fairly similar to this:

public packageDateTime getDateTime() {
package.DateTime toReturn = FreezablePagingLoadConfigAutoBean.this.getWrapped().getFreezeDateTime();
if (toReturn != null) {
if (FreezablePagingLoadConfigAutoBean.this.isWrapped(toReturn)) {
toReturn = FreezablePagingLoadConfigAutoBean.this.getFromWrapper(toReturn);
} else {
toReturn = new auto.bean.package.DateTimeAutoBean(getFactory(), toReturn).as();
}
}
return toReturn;
}

It seems like the generated code checks whether or not the AutoBean is registered and
if not, it creates it in the else clause. 

When I take a look at my generated iterator implementation I find this:

public java.lang.Object next() {
java.lang.Object toReturn = IteratorAutoBean.this.getWrapped().next();
if (toReturn != null) {
if (IteratorAutoBean.this.isWrapped(toReturn)) {
toReturn = IteratorAutoBean.this.getFromWrapper(toReturn);
} else {
// THIS SEEMS TO BE WRONG
}
}
IteratorAutoBean.this.call("next", toReturn );
return toReturn;
}

As you can see the else clause is empty. Here is where the NPE originates from in our
code. It seems like the Iterator is generated wrong (?). 

In our code we try to encode a LoadConfiguration (ExtGWT class) which contains a List
of SortInfoBean (ExtGWT class).

Hope this helps. 

-- artur

Reported by artur.kronenberg on 2012-08-22 08:14:49

dankurka commented 9 years ago
To me this reads as a misuse of AutoBean rather than a bug. 

You should use the Factory interface for creating instances of your AutoBean
This line:
>>IBeanList beanList = new BeanList();
should be:
>>IBeanList beanList = factory.createBeanList();

If you want to wrap an existing instance create a method for it on the factory interface
like this:
>> IBeanList beanList = factory.createBeanList(myBeanList);

Reported by kurka.daniel on 2012-11-12 13:39:36

dankurka commented 9 years ago
No Daniel, per comment #4 and various other reports (e.g. http://stackoverflow.com/q/11530451/116472
) it also happens when the list comes from a wrapped bean.

Reported by t.broyer on 2012-11-12 17:09:05