x-stream / xstream

Serialize Java objects to XML and back again.
http://x-stream.github.io
Other
749 stars 227 forks source link

XStream Converter does not seem to be used #111

Closed janvanrijn closed 6 years ago

janvanrijn commented 6 years ago

I have the following XML (simplified):

<oml:data_qualities>
  <oml:quality>
    <oml:name>MajorityClassSize</oml:name>
    <oml:value>1669.0</oml:value>
  </oml:quality>
  <oml:quality>
    <oml:name>MaxKurtosisOfNumericAtts</oml:name>
    <oml:value />
  </oml:quality>
</oml:data_qualities>

Because of the self-closing tags, I created the following subclass of DoubleConverter:

import org.apache.commons.lang3.StringUtils;

import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.converters.basic.DoubleConverter;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;

public class EmptyDoubleConverter extends DoubleConverter {
    // Handles empty Double tags (e.g., <oml:value /> for quality list)

    public boolean canConvert(Class type) {
        return type.equals(Double.class);
    }

    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
        String value = reader.getValue();
        if (StringUtils.isEmpty(value)) {
            return null;
        }
        return Double.valueOf(value);
    }
}

And register it accordingly:

XStream xstream = new XStream(null,new DomDriver("UTF-8", new NoNameCoder()),clr);
xstream.registerConverter(new EmptyDoubleConverter());
xstream.processAnnotations(DataQuality.class);

When adding a debug message in EmptyDoubleConverter it indicates that it can convert, but it never reaches the unmarshal function.

janvanrijn commented 6 years ago

In order to complete the MWE here is also the DataQualities class (although it seems a bit unwieldy and unnecessary ):

import java.util.HashMap;
import java.util.Map;

import org.openml.apiconnector.settings.Constants;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import com.thoughtworks.xstream.annotations.XStreamImplicit;

@XStreamAlias("oml:data_qualities")
public class DataQuality {

    @XStreamAsAttribute
    @XStreamAlias("xmlns:oml")
    private final String oml = Constants.OPENML_XMLNS;

    @XStreamAlias("xmlns:did")
    private Integer did;

    @XStreamAlias("oml:evaluation_engine_id")
    private Integer evaluation_engine_id;

    @XStreamAlias("oml:error")
    private String error;

    @XStreamImplicit
    @XStreamAlias("oml:quality")
    private Quality[] qualities;

    public DataQuality(Integer did, Integer evaluation_engine_id, Quality[] qualities) {
        this.did = did;
        this.evaluation_engine_id = evaluation_engine_id;
        this.qualities = qualities;
    }

    public DataQuality(Integer did, Integer evaluation_engine_id, String error) {
        this.did = did;
        this.evaluation_engine_id = evaluation_engine_id;
        this.error = error;
    }

    public Integer getDid() {
        return did;
    }

    public Integer getEvaluation_engine_id() {
        return evaluation_engine_id;
    }

    public String getError() {
        return error;
    }

    public Quality[] getQualities() {
        return qualities;
    }

    public String[] getQualityNames() {
        String[] result = new String[qualities.length];
        for (int i = 0; i < qualities.length; ++i) {
            result[i] = qualities[i].getName();
        }
        return result;
    }

    public Map<String,Double> getQualitiesMap() {
        Map<String,Double> qm = new HashMap<String, Double>();
        for (Quality q : qualities) {
            qm.put(q.getName(), q.getValue());
        }
        return qm;
    }

    public String getOml() {
        return oml;
    }

    @Override
    public boolean equals(Object o) {

        if(o != null) {
            if(o instanceof DataQuality) {
                DataQuality qualities = (DataQuality) o;
                if(this.getQualitiesMap().equals(qualities.getQualitiesMap())) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return 11 * this.getQualitiesMap().hashCode();
    }

    @XStreamAlias("oml:quality")
    public static class Quality {
        @XStreamAlias("oml:name")
        private String name;
        @XStreamAlias("oml:feature_index")
        private Integer feature_index;
        @XStreamAlias("oml:value")
        private Double value;

        @XStreamAsAttribute
        @XStreamAlias("oml:interval_start")
        private Integer interval_start;

        @XStreamAsAttribute
        @XStreamAlias("oml:interval_end")
        private Integer interval_end;

        public Quality(String name, Double value) {
            this.name = name;
            this.value = value;
            this.feature_index = null;
        }

        public Quality(String name, Double value, Integer intervat_start, Integer interval_end, Integer index) {
            this.name = name;
            this.value = value;
            this.interval_start = intervat_start;
            this.interval_end = interval_end;
            this.feature_index = index;
        }

        public String getName() {
            return name;
        }

        public Double getValue() {
            return value;
        }

        public Integer getInterval_start() {
            return interval_start;
        }

        public Integer getInterval_end() {
            return interval_end;
        }

        public Integer getFeature_index() {
            return feature_index;
        }

        @Override
        public String toString() {
            return name + ":" + value;
        }
    }
}
joehni commented 6 years ago

DoubleConverter is a SingleValueConverter, yet you did not override fromString. It simply has no unmarshal method, so it is no wonder that the method is never called if you implement one.

janvanrijn commented 6 years ago

I really need to learn how to read, thanks a lot!

For the sake of competency, what would be the easiest way to make the toString method print a self-closing tag? I read this article but it doens't seem to cover it.

joehni commented 6 years ago

This is nothing that can be triggered in the converter of the object itself. A converter is always only responsible for the object's value, never for the surrounding tag. Only if an object consists of multiple values, the converter may create tags for its children and then let XStream select the appropriate converters and write the children's values again.

And it depends solely on the implementation of the XML writer. Most drivers will use XStream's own PrettyPrintWriter though and that one will create an empty tag if startElement and endElement is called without any setValue call in between. However, XStream's converters will typically generate no tag at all for null values.