私は2日間JAXBに苦労しています。私は遭遇した最後の問題に悩まされています。マーシャリングが正しく機能していますが、アンマーシャリングしていません。
2つのカスタムタイプのHashMapを整列化および非整列化したいので、すべてを正しくマーシャリングするXmlAdapterを作成しました。加えて、そこには循環参照があり、このマップでのみ使用される独自の参照クラスを作成して解決しました。
私はここから持ってXmlAdapterのソリューション:
これは、する必要がありますクラスが(ある:私の解決策以下JAXB Annotations - How do I make a list of XmlIDRef elements have the id value as an attribute instead of element body text?
..and循環参照溶液はこちらからUN)は整列化:
RoomModel.java:
package at.jku.buildingsimulator.model;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import at.jku.buildingsimulator.MainApp;
import at.jku.buildingsimulator.jaxb.ConnectionAdapter;
import at.jku.buildingsimulator.jaxb.SensorAdapter;
import at.jku.indoorpersontracker.model.Room;
import at.jku.indoorpersontracker.sensor.Sensor;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
import javafx.collections.ObservableSet;
@XmlType(name = "RoomModel")
public class RoomModel {
@XmlType(name = "WallPosition")
public static enum WallPosition {
LEFT, RIGHT, TOP, BOTTOM, NOT_ADJACENT, DIFFERENT_FLOOR, OUTSIDE;
public static WallPosition opposite(WallPosition position) {
switch(position) {
case LEFT: return RIGHT;
case RIGHT: return LEFT;
case TOP: return BOTTOM;
case BOTTOM: return TOP;
case NOT_ADJACENT: return NOT_ADJACENT;
case DIFFERENT_FLOOR: return DIFFERENT_FLOOR;
case OUTSIDE: return OUTSIDE;
default: return DIFFERENT_FLOOR;
}
}
}
private IntegerProperty roomNumber = new SimpleIntegerProperty();
private ObservableSet<Sensor> sensors = FXCollections.observableSet();
private ObservableSet<PersonModel> persons = FXCollections.observableSet();
private ObservableSet<DeviceModel> devices = FXCollections.observableSet();
private ObservableMap<WallPosition, RoomModel> connectedRooms = FXCollections.observableHashMap();
private Room room; // the underlying room for the indoorpersontracker
public RoomModel() { this(-1); }
public RoomModel(int roomNumber) {
this.roomNumber.set(roomNumber);
this.room = MainApp.getInstance().getTracker().getRoom(roomNumber);
if(this.room == null) {
this.room = new Room(roomNumber);
// MainApp.getInstance().getTracker().addRoom(room);
}
}
/**
* connect this room with another room
* @param other
* @param isConnected true if the rooms are connected or false if they are disconnected
* @param position the position where the door is at
*/
public void setConnected(RoomModel other, boolean isConnected, WallPosition position) {
if (isConnected == true && !connectedRooms.containsValue(other)) { // prevent endless loop
if(this != BuildingModel.getSingleton().getOutsideInstance())
connectedRooms.put(position, other);
System.out.println("adding connection: " + position + ": " + other.getRoomNumber()); // TODO remove
other.setConnected(this, true, WallPosition.opposite(position)); // also tell the other room that it is connected with this room
if(this == BuildingModel.getSingleton().getOutsideInstance()) {
other.getRoom().setHasEntrance(true);
} else if(other == BuildingModel.getSingleton().getOutsideInstance()) {
room.setHasEntrance(true);
} else {
room.setConnected(other.getRoom(), true); // also connect the library rooms
}
} else if (isConnected == false && connectedRooms.containsValue(other)) {
if(this != BuildingModel.getSingleton().getOutsideInstance())
connectedRooms.remove(position);
other.setConnected(this, false, WallPosition.opposite(position));
if(this == BuildingModel.getSingleton().getOutsideInstance()) {
other.getRoom().setHasEntrance(false);
} else if(other == BuildingModel.getSingleton().getOutsideInstance()) {
room.setHasEntrance(false);
} else {
room.setConnected(other.getRoom(), false);
}
}
}
@XmlElement(name = "connections")
@XmlJavaTypeAdapter(ConnectionAdapter.class)
// @XmlElements({ @XmlElement(name = "WallPosition", type = WallPosition.class), @XmlElement(name = "RoomModel", type = RoomModelRef.class) })
public ObservableMap<WallPosition, RoomModel> getConnected() {
return this.connectedRooms;
}
// TODO remove
public void setConnected(ObservableMap<WallPosition, RoomModel> connections) {
this.connectedRooms = connections;
}
public void setHasEntrance(boolean hasEntrance, WallPosition position) {
this.room.setHasEntrance(hasEntrance);
// connect this room with the outside via this wall to draw a door
if(hasEntrance)
this.connectedRooms.put(position, BuildingModel.getSingleton().getOutsideInstance());
else
this.connectedRooms.remove(position);
}
public boolean getHasEntrance() {
return this.room.getHasEntrance();
}
public final IntegerProperty roomNumberProperty() {
return this.roomNumber;
}
@XmlAttribute(name = "roomNumber", required = true)
public final int getRoomNumber() {
return this.roomNumberProperty().get();
}
@XmlTransient
public Room getRoom() {
return room;
}
public void addSensor(Sensor sensor) {
sensors.add(sensor);
}
@XmlElement(name = "Sensor")
@XmlJavaTypeAdapter(SensorAdapter.class)
public ObservableSet<Sensor> getSensors() {
return sensors;
}
public void addPerson(PersonModel person) {
persons.add(person);
}
@XmlTransient
public ObservableSet<PersonModel> getPersons() {
return persons;
}
public void removePerson(PersonModel person) {
persons.remove(person);
}
public void addDevice(DeviceModel device) {
devices.add(device);
}
@XmlElement(name = "DeviceModel", type = DeviceModel.class)
public ObservableSet<DeviceModel> getDevices() {
return devices;
}
public void removeSensor(Sensor sensor) {
sensors.remove(sensor);
System.err.println("Sensor model removed from room " + getRoomNumber());
}
public void removeDevice(DeviceModel deviceModel) {
devices.remove(deviceModel);
}
public boolean hasTopWallDoor() {
return connectedRooms.containsKey(WallPosition.TOP);
}
public boolean hasBottomWallDoor() {
return connectedRooms.containsKey(WallPosition.BOTTOM);
}
public boolean hasLeftWallDoor() {
return connectedRooms.containsKey(WallPosition.LEFT);
}
public boolean hasRightWallDoor() {
return connectedRooms.containsKey(WallPosition.RIGHT);
}
public void setRoomNumber(int roomNumber) {
this.roomNumber.set(roomNumber);
this.room.setRoomNumber(roomNumber);
// migrate the persons in this room to the new room number
/*SimulatedSensor tempSensor = new SimulatedSensor(0);
// tempSensor.setRoom(room); TODO is currently in FloorModel.setRoomNoOffset, move to here if setting room number can be done elsewhere (could cause a problem with order then, add a Platform.runLater() here to resolve this)
MainApp.getInstance().getTracker().addSensor(tempSensor, roomNumber);
for(PersonModel person : persons) {
tempSensor.detectPerson(person.getName());
}
MainApp.getInstance().getTracker().removeSensor(tempSensor);*/
}
public boolean isConnectedWith(RoomModel roomModel) {
return connectedRooms.containsValue(roomModel);
}
@Override
public boolean equals(Object obj) {
if(obj == this) {
return true;
} else if(obj instanceof RoomModel) {
RoomModel roomModel = (RoomModel)obj;
return roomModel.room.equals(this.room);
} else {
return false;
}
}
@XmlID
public String getId() {
return String.valueOf(getRoomNumber());
}
void afterUnmarshal(Unmarshaller u, Object parent) {
System.err.println("connections: " + connectedRooms.size());
}
}
ConnectionAdapter.java:
package at.jku.buildingsimulator.jaxb;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import at.jku.buildingsimulator.model.RoomModel;
import at.jku.buildingsimulator.model.RoomModel.WallPosition;
public class ConnectionAdapter extends XmlAdapter<ConnectionAdapter.AdaptedMap, Map<WallPosition, RoomModel>> {
public static class AdaptedMap {
//@XmlVariableNode("key")
@XmlElement(name = "connection", type = AdaptedEntry.class)
List<AdaptedEntry> entries = new ArrayList<AdaptedEntry>();
}
public static class AdaptedEntry {
@XmlElement(name = "WallPosition")
public WallPosition key;
@XmlElement(name="RoomModel")
@XmlJavaTypeAdapter(RoomModelAdapter.class)
public RoomModel value;
}
@Override
public AdaptedMap marshal(Map<WallPosition, RoomModel> map) throws Exception {
AdaptedMap adaptedMap = new AdaptedMap();
for(Entry<WallPosition, RoomModel> entry : map.entrySet()) {
AdaptedEntry adaptedEntry = new AdaptedEntry();
adaptedEntry.key = entry.getKey();
adaptedEntry.value = entry.getValue();
adaptedMap.entries.add(adaptedEntry);
}
return adaptedMap;
}
@Override
public Map<WallPosition, RoomModel> unmarshal(AdaptedMap adaptedMap) throws Exception {
List<AdaptedEntry> adaptedEntries = adaptedMap.entries;
Map<WallPosition, RoomModel> map = new HashMap<>(adaptedEntries.size());
for(AdaptedEntry adaptedEntry : adaptedEntries) {
map.put(adaptedEntry.key, adaptedEntry.value);
}
System.err.println("unmarshal: " + map.size());
for(Entry<WallPosition, RoomModel> entry : map.entrySet()) {
System.err.println(entry.getKey() + ", " + entry.getValue());
}
return map;
}
}
RoomModelAdapter.java:
package at.jku.buildingsimulator.jaxb;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import at.jku.buildingsimulator.model.RoomModel;
public class RoomModelAdapter extends XmlAdapter<RoomModelRef, RoomModel>{
@Override
public RoomModelRef marshal(RoomModel v) throws Exception {
return new RoomModelRef(v);
}
@Override
public RoomModel unmarshal(RoomModelRef v) throws Exception {
return v.roomModel;
}
}
RoomModelRef.java:
package at.jku.buildingsimulator.jaxb;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlIDREF;
import at.jku.buildingsimulator.model.RoomModel;
public class RoomModelRef {
@XmlIDREF
@XmlAttribute(name = "ref")
RoomModel roomModel;
public RoomModelRef() { }
public RoomModelRef(RoomModel roomModel) {
this.roomModel = roomModel;
}
}
そして、ここで得られた例示の.xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<BuildingModel>
<FloorModel floorNo="1" numRooms="4" roomNoOffset="0">
<roomModels>
<entry>
<key>1</key>
<value roomNumber="1">
<connections>
<connection>
<WallPosition>RIGHT</WallPosition>
<RoomModel ref="2"/>
</connection>
</connections>
<id>1</id>
</value>
</entry>
<entry>
<key>2</key>
<value roomNumber="2">
<connections>
<connection>
<WallPosition>LEFT</WallPosition>
<RoomModel ref="1"/>
</connection>
<connection>
<WallPosition>BOTTOM</WallPosition>
<RoomModel ref="4"/>
</connection>
</connections>
<id>2</id>
</value>
</entry>
<entry>
<key>3</key>
<value roomNumber="3">
<connections/>
<id>3</id>
</value>
</entry>
<entry>
<key>4</key>
<value roomNumber="4">
<connections>
<connection>
<WallPosition>RIGHT</WallPosition>
<RoomModel ref="0"/>
</connection>
<connection>
<WallPosition>TOP</WallPosition>
<RoomModel ref="2"/>
</connection>
</connections>
<id>4</id>
</value>
</entry>
</roomModels>
</FloorModel>
<outsideInstance roomNumber="0">
<connections/>
<id>0</id>
</outsideInstance>
<persons/>
</BuildingModel>
私はそこにいくつかのデバッグ行を置いています: unmarshal()メソッドのConnectionAdapterでは、最初の部屋では "unmarshal:1"と表示されます。 そして、afterUnmarshal()のRoomModelで、最初の部屋の "connections:0"と書いてありますが、明らかにConnectionAdapterでアンマーシャリングが機能するので、あまり得られません。
残念ながら、このモデルには非常に複雑でこの問題に関連していないため、モデルコード全体を投稿することはできません。
編集:出力をfollwingが得られている
@Override
public Map<WallPosition, RoomModel> unmarshal(AdaptedMap adaptedMap) throws Exception {
List<AdaptedEntry> adaptedEntries = adaptedMap.entries;
Map<WallPosition, RoomModel> map = new HashMap<>(adaptedEntries.size());
for(AdaptedEntry adaptedEntry : adaptedEntries) {
map.put(adaptedEntry.key, adaptedEntry.value);
}
System.err.println("unmarshal: " + map.size());
for(Entry<WallPosition, RoomModel> entry : map.entrySet()) {
System.err.println(entry.getKey() + ", " + entry.getValue());
}
return map;
}
:
unmarshal: 1
RIGHT, null
connections: 0
unmarshal: 2
LEFT, [email protected]
BOTTOM, null
connections: 0
unmarshal: 0
connections: 0
unmarshal: 2
RIGHT, null
TOP, [email protected]
connections: 0
Rooms in here: 4
unmarshal: 0
connections: 0
だから、明らかにRoomModel(あたり大丈夫、私はConnectionAdapterでアンマーシャリング()メソッドでは、今非整列化マップをプリントアウト参照)は、毎回正しくアンマーシャリングされていません。