extensible XmlAdapter for Map bound types


References

Java Sources

When keys are not related to values

When keys can be derived from values

Apache Maven

<dependency>
  <groupId>com.googlecode.jinahya</groupId>
  <artifactId>jinahya-se</artifactId>
  <version>@@?</version>
<dependency>

Example Classes

Here are Employee and Department we gonna use.

public class Employee {

    @XmlElement
    private long id;

    @XmlElement
    private String name;

    @XmlElement
    private int age;
}
@XmlTransient
public abstract class Department {

    public Map<Long, Employee> getEmployees() {
        if (employees == null) {
            employees = new HashMap<>();
        }
        return employees;
    }

    private Map<Long, Employee> employees;
}

Canonical Approach

There are three abstract classes for extension.

@XmlTransient
public abstract class MapEntry<K, V> {

    protected K getKey();

    protected void setKey(K key);

    protected V getValue();

    protected void setValue(V value);
}
@XmlTransient
public abstract class MapEntries<E extends MapEntry<K, V>, K, V> {

    public MapEntries(final Class<E> entryType);

    protected List<E> getEntries();
}
public abstract class MapEntriesAdapter<T extends MapEntries<?, K, V>, K, V>
    extends XmlAdapter<T, Map<K, V>> {

    public MapEntriesAdapter(final Class<T> entriesType);
}

Note that both MapEntry and MapEntries are both annotated with @XmlTransient.

public class EmployeeEntry extends MapEntry<Long, Employee> {

    @XmlAttribute
    public Long getId() {
        return getKey();
    }

    public void setId(final Long id) {
        setKey(id);
    }

    @XmlElement
    public Employee getEmployee() {
        return getValue();
    }

    public void setEmployee(final Employee employee) {
        setValue(employee);
    }
}
public class EmployeeEntries extends MapEntries<EmployeeEntry, Long, Employee> {

    public EmployeeEntries() {
        super(EmployeeEntry.class);
    }

    @XmlElement(name = "employeeEntry")
    public List<EmployeeEntry> getEmployeeEntries() {
        return getEntries();
    }
}
public class EmployeesAdapter0
    extends MapEntriesAdapter<EmployeeEntries, Long, Employee> {

    public EmployeesAdapter0() {
        super(EmployeeEntries.class);
    }
}
@XmlRootElement
public class Department0 extends Department {

    @XmlElement
    @XmlJavaTypeAdapter(EmployeesAdapter0.class)
    @Override
    public Map<Long, Employee> getEmployees() {
        return super.getEmployees();
    }
}
<department0 xmlns="http://jinahya.googlecode.com/xml/bind/test/map">
    <employees>
        <employeeEntry>
            <id>0</id>
            <employee>
                <id>0</id>
                <name>name0</name>
                <age>20</age>
            </employee>
        </employeeEntry>
        <employeeEntry>
            <id>1</id>
            <employee>
                <id>1</id>
                <name>name1</name>
                <age>21</age>
            </employee>
        </employeeEntry>
        <employeeEntry>
            <id>2</id>
            <employee>
                <id>2</id>
                <name>name2</name>
                <age>22</age>
            </employee>
        </employeeEntry>
    </employees>
</department0>

When keys can be derived from values

Why <employeeEntry> elements are there? Because above Canonical Approach assumed that those keys and values are very different types. So what if those keys can be derived from values?
There are another abstract classes for this case.

@XmlTransient
public abstract class MapValues<V> {

    protected List<V> getValues();
}
public abstract class MapValuesAdapter<T extends MapValues<V>, K, V>
    extends XmlAdapter<T, Map<K, V>> {

    public MapValuesAdapter(final Class<T> mapValuesType);

    protected abstract K getKey(V value);
}
public class EmployeeValues extends MapValues<Employee> {

    @XmlElement(name = "employee")
    public List<Employee> getEmployees() {
        return super.getValues();
    }
}

Now we can create another type of XmlAdapter.

public class EmployeesAdapter2
    extends MapValuesAdapter<EmployeeValues, Long, Employee> {

    public EmployeesAdapter2() {
        super(EmployeeValues.class);
    }

    @Override
    protected Long getKey(final Employee value) {
        return value.getId();
    }
}
@XmlRootElement
public class Department2 extends Department {

    @XmlElement
    @XmlJavaTypeAdapter(EmployeesAdapter2.class)
    @Override
    public Map<Long, Employee> getEmployees() {
        return super.getEmployees();
    }
}

Now we got this.

<department2 xmlns="http://jinahya.googlecode.com/xml/bind/test/map">
    <employees>
        <employee>
            <id>0</id>
            <name>name0</name>
            <age>20</age>
        </employee>
        <employee>
            <id>1</id>
            <name>name1</name>
            <age>21</age>
        </employee>
        <employee>
            <id>2</id>
            <name>name2</name>
            <age>22</age>
        </employee>
    </employees>
</department2>

When there are no siblings

Note that those classes explained so far are for general properties with siblings. When a Department doesn’t have any other properties but the Employee list, there is no need to work with XmlAdapter at all.

@XmlRootElement
public class Department7 extends AbstractDepartment {

    @XmlElement(name = "employee")
    private List<Employee> getEmployeeList() {
        final Collection<Employee> employees = getEmployees().values();
        if (employees instanceof List) {
            return (List<Employee>) employees;
        }
        return new ArrayList<>(employees);
    }

    private void setEmployeeList(final List<Employee> employeeList) {
        for (Employee employee : employeeList) {
            getEmployees().put(employee.getId(), employee);
        }
    }

    // not even need to be overridden; just informed.
    @XmlTransient
    @Override
    public Map<Long, Employee> getEmployees() {
        return super.getEmployees();
    }
}

Here comes a little bit more concise version.

<department7 xmlns="http://jinahya.googlecode.com/xml/bind/test/map">
    <employee>
        <id>0</id>
        <name>name0</name>
        <age>20</age>
    </employee>
    <employee>
        <id>1</id>
        <name>name1</name>
        <age>21</age>
    </employee>
    <employee>
        <id>2</id>
        <name>name2</name>
        <age>22</age>
    </employee>
</department7>
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s