Donnerstag, 14. März 2013

JAXB und HashMaps, Teil 2

In Teil 1 zu JAXB und HashMaps ging es vor einiger Zeit darum, mit möglichst wenig Aufwand im Java-Code eine XML-Struktur zu verarbeiten, deren Element-Namen teilweise fest vorgegeben waren. Teil 2 zeigt nun, wie man eine XML-Struktur mit beliebigen Element-Namen aus einer (Hash)Map innerhalb einer JAXB-Entität ausgibt (bzw. dorthin einliest). Konkret soll folgendes XML-Dokument erzeugt werden:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<meineEntitaet>
  <paarliste>
    <paar>
      <schluessel>Antwort</schluessel>
      <wert>42</wert>
    </paar>
  </paarliste>
</meineEntitaet>
Dazu benötigen wir als Erstes für einen einzelnen Eintrag der Map (also für jedes Schlüssel-Wert-Paar) folgende Klasse:

import javax.xml.bind.annotation.XmlElement;

class MapElement {

  @XmlElement(name = "schluessel")
  String key;

  @XmlElement(name = "wert")
  Integer value;

  MapElement() {
  }

  MapElement(String key, Integer value) {
    this.key = key;
    this.value = value;
  }
}

Die Liste der Map-Einträge wird in einer eigenen Klasse verpackt, damit wir den Namen des XML-Elements festlegen können, das die einzelnen Schlüssel-Wert-Paare umschließt:

class MapElements {

  @XmlElement(name = "paar")
  List<MapElement> mapElements;

  MapElements() {
  }

  MapElements(List<MapElement> mapElements) {
    this.mapElements = mapElements;
  }
}

In der JAXB-Entität taucht nun aber nicht diese MapElements-Klasse auf, sondern die ursprünglich gewünschte (Hash)Map:

import java.util.*;
import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class MeineEntitaet {

  @XmlElement(name = "paarliste")
  @XmlJavaTypeAdapter(MapAdapter.class)
  private Map<String, Integer> map = new HashMap<>();
  public Map<String, Integer> getMap() {
    return map;
  }

  public void setMap(Map<String, Integer> map) {
    this.map = map;
  }
}

Für die Umwandlung zwischen der MapElements-Klasse und der HashMap benötigen wir einen XmlAdapter:

import java.util.*;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class MapAdapter extends XmlAdapter<MapElements, Map<String, Integer>> {

  @Override
  public MapElements marshal(Map<String, Integer> map) {
    List<MapElement> list = new ArrayList<>();
    for (Map.Entry<String, Integer> entry : map.entrySet()) {
      list.add(new MapElement(entry.getKey(), entry.getValue()));
    }
    return new MapElements(list);
  }

  @Override
  public Map<String, Integer> unmarshal(MapElements elements) {
    Map<String, Integer> map = new HashMap<>();
    for (MapElement mapElement : elements.mapElements) {
      map.put(mapElement.key, mapElement.value);
    }
    return map;
  }
}

Damit können wir schließlich das XML-Dokument erzeugen:
MeineEntitaet huelle = new MeineEntitaet();
huelle.getMap().put("Antwort", 42);
JAXB.marshal(huelle, System.out);