2016-09-15 4 views
0

私は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?

JAXB Map adapter

..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でアンマーシャリング()メソッドでは、今非整列化マップをプリントアウト参照)は、毎回正しくアンマーシャリングされていません。

答えて

0

私は今自分で解決策を考え出しました。

問題は、接続の参照が読み取られたときにすべてのRoomModelが読み込まれていないことが原因で、アンマーシャリングの順序が問題でした。 JAXBはこのリファレンスを保存して後で解決するほど賢明だと思っていましたが、そうではありません。

私は単に私のモデルを再加工することで問題を解決しました。特に、循環参照を削除してシリアライゼーションを容易にするために、connectedRoomsマップのタイプを<WallPosition, Integer>に変更しました。なぜ私は以前にそれについて考えていたのか分かりませんし、この問題のために費やした仕事と時間について考えると、私にはスリルが与えられますが、少なくとも循環参考文献の経験があり、マーシャル問題。

このスレッドを削除する必要があるかどうか、または他の誰かを助けることができるかどうかはわかりませんが、少なくともこれは閉じることができます。

関連する問題