tags:

views:

1100

answers:

1

Using Dozer to map two objects, I have:

/**
/* This first class uses the GXT (ExtJS) framework
**/
Class1 extends BaseModelData
{
    public int getId()
    {
     return (Integer)get("id");
    }

    public void setId(int id)
    {
     set("id", id);
    }

    // more properties
}

Class2
{
    public int getId()
    {
     return id;
    }

    public void setId(int id)
    {
     this.id = id;
    }

    // more properties
}

If I don't set the Id in the first class (by calling class1.setId()) the result is a NullPointerException from Dozer. I understand that this is correct as get("id") would be null.

I can of course resolve this by putting in a check for null, and returning -1 or 0, or whatever.

The problem is that this then becomes a runtime error rather than a compile time error. I'd much prefer to resolve this correctly.

Now I've read in the Dozer documentation that you can have it skip null by doing map-null="false", but I couldn't get this to work...

Any suggestions?

+1  A: 

Hi,

I believe the problem is not in dozer, but in the hidden auto-unboxing in your getter:

   public int getId()
{
    return (Integer)get("id");
}

(Integer)get("id") is implicitely cast into an int because the return type of your method is "int".

This will work in most cases... EXCEPT when the result is null, in which case you get a NullPointerException because an int may never be null.

This results in hidden NullPointerExceptions... More info here: http://www.theserverside.com/blogs/thread.tss?thread_id=41731

To resolve this, you have multiple choices:

  • If Class1 and Class2 may in fact contain a null id, you want to modify your getters/setters to get/set Integers instead of primitive ints.

  • If both Class1 and Class2 should never contain a null id, and you consider this to be a class invariant, you may keep the primitive int type in the getter/setter and either:

    • Make sure get("id") will never be null, by initializing it to some specific value (such as 0) in the constructor), and making sure that nothing can set it to null.
    • Or decide that getId() will return a default value if null, and add a null check in the getter as you said.
  • If Class1 may have a null id, but Class2 may not, you should have Class1's getters and setters use an Integer type instead of an int primitive, and you should create a dozer CustomConverter that returns a default value when the source field is null.

Regards


[EDIT] Here is the test code that shows that Dozer does ignore mapping nulls when asked to:

src/com/test/dozer/Class1.java :

package com.test.dozer;

import com.extjs.gxt.ui.client.data.BaseModelData;

public class Class1 extends BaseModelData {

    // Notice the return type here: "Integer" and *not* int
    // Returning int throws a NullPointerException when get("id") is null!
    public Integer getId() {
     return (Integer) get("id");
    }

    public void setId(Integer id) {
     set("id", id);
    }

}


src/com/test/dozer/Class2.java :

package com.test.dozer;

public class Class2 {

    private int id;

    public void setId(int id) {
     this.id = id;
    }

    public int getId() {
     return id;
    }
}


src/dozerMappingFile.xml :

<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozer.sourceforge.net"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://dozer.sourceforge.net http://dozer.sourceforge.net/schema/beanmapping.xsd"&gt;

  <configuration>
    <stop-on-errors>true</stop-on-errors>
    <date-format>MM/dd/yyyy HH:mm</date-format><!-- default dateformat will apply to all class maps unless the class mapping explicitly overrides it -->
    <wildcard>true</wildcard><!-- default wildcard policy that will apply to all class maps unless the class mapping explicitly overrides it -->
  </configuration>

  <mapping map-null="false">
    <class-a>com.test.dozer.Class1</class-a>
    <class-b>com.test.dozer.Class2</class-b>
  </mapping>

</mappings>


src/com/test/dozer/DozerTest.java :

package com.test.dozer;

import java.util.Arrays;

import junit.framework.Assert;

import org.dozer.DozerBeanMapper;
import org.junit.Before;
import org.junit.Test;

public class DozerTest {

    private DozerBeanMapper mapper;

    @Before
    public void setUp() {
     mapper = new DozerBeanMapper(Arrays.asList("dozerMappingFile.xml"));
    }

    /**
     * Verifies that class1's id is mapped into class2's id when not null.
     */
    @Test
    public void testMappingWhenIdNotNull() {
     Class1 class1 = new Class1();
     class1.setId(1);
     Class2 class2 = new Class2();
     class2.setId(2);

     mapper.map(class1, class2);

     Assert.assertEquals(1, class2.getId());
    }

    /**
     * Verifies that class2's id is not set to null when class1's id is null.
     */
    @Test
    public void testMappingWhenIdIsNull() {
     Class1 class1 = new Class1();
     Class2 class2 = new Class2();
     class2.setId(2);

     mapper.map(class1, class2);

     Assert.assertEquals(2, class2.getId());
    }

}
eneveu
I agree with everything you say, but from what I understand it's also possible to have dozer skip mappings to null values. This way the mapping software doesn't map invalid fields.
Stephane Grenier
It is indeed possible to have Dozer skip null mappings, by using the map-null="false" parameter.The problem is that Dozer will call getId() to determinate if it is null or not, and if not it will map it. But getId() will fail due to the auto-unboxing from null to int...If you replace your getter to return "Integer" instead of "int", it works.I will edit my answer above with the test to show it.
eneveu