虽然编组工作正常,但JAXB映射不能解组

时间:2016-09-15 15:33:59

标签: java jaxb

我现在已经在JAXB上挣扎了两天而且我遇到了我遇到的最后一个问题,我自己似乎无法解决,因为这是一个非常具体的问题和谷歌无法帮助我..

我想编组和解组两个自定义类型的HashMap,因此我编写了一个XmlAdapter来正确编组所有内容。另外我在那里有循环引用,我通过创建一个仅用于此映射的自己的Reference类来解决。

我从这里获得的XmlAdapter解决方案:JAXB Map adapter

..循环参考解决方案来自:JAXB Annotations - How do I make a list of XmlIDRef elements have the id value as an attribute instead of element body text?

在我的解决方案之下:

这是需要(联合国)编组的类:

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(每个引用)每次都没有正确解组..

1 个答案:

答案 0 :(得分:0)

好的,我现在想出了一个解决方案。

问题在于解组的顺序,这导致在读取连接中的引用时,并非所有RoomModel都已被读入。我认为JAXB会非常聪明地保存这个引用并在以后解决它,但不,它不是。

我通过简单地重新处理我的模型来解决问题,特别是我只是将地图connectedRooms的类型更改为<WallPosition, Integer>以删除循环引用并简化序列化。我真的不知道为什么我之前想到了这一点,当我想到我为这个问题花费的工作和时间时,它给了我惊险刺激,但至少我获得了一些循环引用的经验和(un)编组问题。

我不知道这个帖子是否应该被删除,或者它是否可以帮助其他人,对我来说至少可以关闭它。