Пользовательский конвертер XStream создает непредвиденный вывод для ссылочных объектов - PullRequest
0 голосов
/ 01 января 2019

Для ObjectProperty Я создал пользовательский конвертер для XStream:

public class ObjectPropertyConverter extends AbstractPropertyConverter<Object> implements Converter {

    public ObjectPropertyConverter(Mapper mapper) {
        super(ObjectProperty.class, mapper);
    }

    @Override
    protected WritableValue<Object> createProperty() {
        return new SimpleObjectProperty();
    }

    @Override
    protected Class readType(HierarchicalStreamReader reader) {
        return mapper.realClass(reader.getAttribute("propertyClass"));
    }

    @Override
    protected void writeValue(HierarchicalStreamWriter writer, MarshallingContext context, Object value) {
        final Class clazz = value.getClass();
        final String propertyClass = mapper.serializedClass(clazz);
        writer.addAttribute("propertyClass", propertyClass);
        context.convertAnother(value);
    }
    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
        WritableValue<Object> property = createProperty();
        if (reader.getAttribute("propertyClass") != null) {
            final Object value = context.convertAnother(null, readType(reader));
            property.setValue(value);
        }
        return property;
    }
}
public abstract class AbstractPropertyConverter<T> implements Converter {

    private final Class clazz;
    protected final Mapper mapper;


    public AbstractPropertyConverter(Class clazz, Mapper mapper) {
        this.clazz = clazz;
        this.mapper = mapper;
    }

    public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
        if (source != null) {
            T value = ((ObservableValue<T>) source).getValue();
            if (value != null) {
                writeValue(writer, context, value);
            }
        }
    }

    protected void writeValue(HierarchicalStreamWriter writer, MarshallingContext context, T value) {
        context.convertAnother(value);
    }

    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
        WritableValue<T> property = createProperty();
        final T value = (T) context.convertAnother(null, readType(reader));
        property.setValue(value);
        return property;
    }

    protected abstract WritableValue<T> createProperty();

    protected abstract Class<? extends T> readType(HierarchicalStreamReader reader);

    public boolean canConvert(Class type) {
        return clazz.isAssignableFrom(type);
    }

}

В большинстве случаев это отлично работает.Однако я столкнулся с проблемой, когда есть список элементов, которые ссылаются друг на друга через ObjectProperty.Пока связанный элемент находится в списке, прежде чем он будет связан с ним, он работает и используется ссылка.Однако, наоборот, это не работает.Вот пример приложения для создания указанной ситуации:

public class ObjectPropertyReferenceSerializer {

    public static void main(String[] args) {
        Item item1 = new Item("item1");
        Item item2 = new Item("item2");
        Item item3 = new Item("item3");
        item2.setParent(item1);
        item1.setParent(item3);
        List<Item> list = asList(item1, item2, item3);
        XStream xStream = getXstreamObject();

        // Save to file
        String filename = "xStreamCrayersWithReference.xml";
        try {
            xStream.toXML(list, Files.newOutputStream(Paths.get(filename), StandardOpenOption.CREATE));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static XStream getXstreamObject() {
        XStream xstream = new XStream(); // DomDriver and StaxDriver instances also can be used with constructor
        xstream.setMode(XStream.ID_REFERENCES);
        xstream.autodetectAnnotations(true);
        xstream.registerConverter(new ObjectPropertyConverter(xstream.getMapper()));
        return xstream;
    }
}

И этот вывод создан:

<java.util.Arrays_-ArrayList id="1">
  <a class="ch.sahits.game.openpatrician.app.model.Item-array" id="2">
    <ch.sahits.game.openpatrician.app.model.Item id="3">
      <uuid>3e0d8b93-b283-4244-997f-d78e7f6e1954</uuid>
      <name>item1</name>
      <parent class="javafx.beans.property.SimpleObjectProperty" id="4" propertyClass="ch.sahits.game.openpatrician.app.model.Item">
        <uuid>fb2ca551-8a0d-450c-966f-4261fdde1d7d</uuid>
        <name>item3</name>
        <parent class="javafx.beans.property.SimpleObjectProperty" id="6"/>
      </parent>
    </ch.sahits.game.openpatrician.app.model.Item>
    <ch.sahits.game.openpatrician.app.model.Item id="7">
      <uuid>524e325c-188a-47d5-914d-53581461e6c9</uuid>
      <name>item2</name>
      <parent class="javafx.beans.property.SimpleObjectProperty" id="8" propertyClass="ch.sahits.game.openpatrician.app.model.Item" reference="3"/>
    </ch.sahits.game.openpatrician.app.model.Item>
    <ch.sahits.game.openpatrician.app.model.Item id="9">
      <uuid>fb2ca551-8a0d-450c-966f-4261fdde1d7d</uuid>
      <name>item3</name>
      <parent class="javafx.beans.property.SimpleObjectProperty" reference="6"/>
    </ch.sahits.game.openpatrician.app.model.Item>
  </a>
</java.util.Arrays_-ArrayList>

Элементы с идентификаторами 4 и 9 одинаковы.Только когда он добавляется как значение ObjectProperty, он встраивается, что означает, что отсутствует самый внешний элемент ().

Как получить преобразователь для выдачи выходных данных, на которые можно правильно ссылаться?

1 Ответ

0 голосов
/ 01 января 2019

Замена конвертера на более простой подход, кажется, работает:

public class ObjectPropertyConverterV2 implements Converter {

    private Mapper mapper;

    public ObjectPropertyConverterV2(Mapper mapper) {
        this.mapper = mapper;
    }

    @Override
    public void marshal(Object o, HierarchicalStreamWriter hierarchicalStreamWriter, MarshallingContext marshallingContext) {
        ObjectProperty objProperty = (ObjectProperty) o;
        if (objProperty.get() != null) {
            hierarchicalStreamWriter.startNode(mapper.serializedClass(objProperty.get().getClass()));
            marshallingContext.convertAnother(objProperty.get());
            hierarchicalStreamWriter.endNode();
        }
    }

    @Override
    public Object unmarshal(HierarchicalStreamReader hierarchicalStreamReader, UnmarshallingContext unmarshallingContext) {
        String type = hierarchicalStreamReader.getAttribute("class");
        ObjectProperty objProperty;
        try {
            objProperty = (ObjectProperty) getClass().getClassLoader().loadClass(type).newInstance();
        } catch (ClassNotFoundException|InstantiationException|IllegalAccessException e) {
            log.warn("Failed to create instance of type " + type, e);
            objProperty = new SimpleObjectProperty();
        }

        if (hierarchicalStreamReader.hasMoreChildren()) {
            hierarchicalStreamReader.moveDown();
            Object value = unmarshallingContext.convertAnother(null, mapper.realClass(hierarchicalStreamReader.getNodeName()));
            hierarchicalStreamReader.moveUp();
            objProperty.setValue(value);
        }
        return objProperty;
    }

    @Override
    public boolean canConvert(Class aClass) {
        return ObjectProperty.class.isAssignableFrom(aClass);
    }
}
...