我现在已经在JAXB上挣扎了两天而且我遇到了我遇到的最后一个问题,我自己似乎无法解决,因为这是一个非常具体的问题和谷歌无法帮助我..
我想编组和解组两个自定义类型的HashMap,因此我编写了一个XmlAdapter来正确编组所有内容。另外我在那里有循环引用,我通过创建一个仅用于此映射的自己的Reference类来解决。
我从这里获得的XmlAdapter解决方案:JAXB Map adapter
在我的解决方案之下:
这是需要(联合国)编组的类:
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>
我还在其中放了一些调试行: 在ConnectionAdapter的unmarshal()方法中,它说:&#34; unmarshal:1&#34;第一个房间。 在AfterUnmarshal()的RoomModel中,它说:&#34;连接:0&#34;对于第一个房间,我不太明白,因为很明显解组在ConnectionAdapter中工作,所以错误必须介于两者之间。
我希望有人花时间给它一个&#34;快速&#34;看,不幸的是我无法发布整个模型代码,因为它非常复杂,与此问题无关。
编辑:好吧我现在在ConnectionAdapter的unmarshall()方法中打印出了解组的地图:
@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, at.jku.buildingsimulator.model.RoomModel@21f654e2
BOTTOM, null
connections: 0
unmarshal: 0
connections: 0
unmarshal: 2
RIGHT, null
TOP, at.jku.buildingsimulator.model.RoomModel@2b13651e
connections: 0
Rooms in here: 4
unmarshal: 0
connections: 0
所以显然RoomModel(每个引用)每次都没有正确解组..
答案 0 :(得分:0)
好的,我现在想出了一个解决方案。
问题在于解组的顺序,这导致在读取连接中的引用时,并非所有RoomModel都已被读入。我认为JAXB会非常聪明地保存这个引用并在以后解决它,但不,它不是。
我通过简单地重新处理我的模型来解决问题,特别是我只是将地图connectedRooms的类型更改为<WallPosition, Integer>
以删除循环引用并简化序列化。我真的不知道为什么我之前想到了这一点,当我想到我为这个问题花费的工作和时间时,它给了我惊险刺激,但至少我获得了一些循环引用的经验和(un)编组问题。
我不知道这个帖子是否应该被删除,或者它是否可以帮助其他人,对我来说至少可以关闭它。