tags:

views:

81

answers:

2

There's ugly XML file that has to be unmarshalled:

<?xml version="1.0" ?>
<configuration>
    <section name="default_options">
        <value name="default_port">8081</value>
        <value name="log_level">WARNING</value>
    </section>
    <section name="custom_options">
        <value name="memory">64M</value>
        <value name="compatibility">yes</value>
    </section>
</configuration>

Resulting Java Objects should be:

public class DefaultOptions {
    private int defaultPort;
    private String logLevel;
    // etc...
}

public class CustomOptions {
    private String memory;
    private String compatibility;
    // etc...
}

This question's answer is very close but I can't figure out the final solution.

+2  A: 

You may create a separate classes to represent structure of your XML:

public class Section {
    @XmlAttribute
    public String name;
    @XmlElement(name = "value")
    public List<Value> values;
}

public class Value {
    @XmlAttribute
    public String name;
    @XmlValue
    public String value;
}

and then use an XmlAdapter to perform conversion:

public class OptionsAdapter extends XmlAdapter<Section, Options> {
    public Options unmarshal(Section s) {
        if ("default_options".equals(s.name)) {
            ...
        } else if (...) {
            ...
        }
        ...
    }
    ...
}

@XmlElement
public class Configuration {
    @XmlElement(name = "section")
    @XmlJavaTypeAdapter(OptionsAdapter.class)
    public List<Options> options;
}

public class DefaultOptions extends Options { ... }
public class CustomOptions extends Options { ... }
axtavt
Interesting solution. First of, thank you. I'll try it. Is it the only way to solve the problem? Or maybe there are a little bit more elegant solutions without "if ("default_options".equals(s.name))"?
Artyom Sokolov
@Artyom: Since JAXB can't deduce the target class from the attribute value, it requires some kind of manual dipatching anyway. Of course, you can do it in more abstract way, e.g. use a map from attribute values to options factories.
axtavt
+1  A: 

How about?

Introduce a common super class called Options:

import javax.xml.bind.annotation.XmlAttribute;

public abstract class Options {

    private String name;

    @XmlAttribute
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Then on your class with the list of options (Configuration in this example), specify an @XmlJavaTypeAdapter on that property:

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
public class Configuration {

    private List<Options> section = new ArrayList<Options>();

    @XmlJavaTypeAdapter(OptionsAdapter.class)
    public List<Options> getSection() {
        return section;
    }

    public void setSection(List<Options> section) {
        this.section = section;
    }

}

The XmlAdapter will look something like this:

import javax.xml.bind.annotation.adapters.XmlAdapter;

public class OptionsAdapter extends XmlAdapter<AdaptedOptions, Options> {

    @Override
    public Options unmarshal(AdaptedOptions v) throws Exception {
        if("default_options".equals(v.name)) {
            DefaultOptions options = new DefaultOptions();
            options.setName(v.getName());
            options.setDefaultPort(Integer.valueOf(v.map.get("default_port")));
            options.setLogLevel(v.map.get("log_level"));
            return options;
        } else {
            CustomOptions options = new CustomOptions();
            options.setName(v.getName());
            options.setCompatibility(v.map.get("compatibility"));
            options.setMemory(v.map.get("memory"));
            return options;
        }
    }

    @Override
    public AdaptedOptions marshal(Options v) throws Exception {
        AdaptedOptions adaptedOptions = new AdaptedOptions();
        adaptedOptions.setName(v.getName());
        if(DefaultOptions.class == v.getClass()) {
            DefaultOptions options = (DefaultOptions) v;
            adaptedOptions.map.put("default_port", String.valueOf(options.getDefaultPort()));
            adaptedOptions.map.put("log_level", options.getLogLevel());
        } else {
            CustomOptions options = (CustomOptions) v;
            adaptedOptions.map.put("compatibility", options.getCompatibility());
            adaptedOptions.map.put("memory", options.getMemory());
        }
        return adaptedOptions;
    }

}

AdaptedOptions looks like:

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.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlValue;

public class AdaptedOptions extends Options {

    @XmlAttribute String name;
    @XmlElement List<Value> value = new ArrayList<Value>();
    Map<String, String> map = new HashMap<String, String>();

    public void beforeMarshal(Marshaller marshaller) {
        for(Entry<String, String> entry : map.entrySet()) {
            Value aValue = new Value();
            aValue.name = entry.getKey();
            aValue.value = entry.getValue();
            value.add(aValue);
        }
    }

    public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
        for(Value aValue : value) {
            map.put(aValue.name, aValue.value);
        }
    }

    private static class Value {
        @XmlAttribute String name;
        @XmlValue String value;
    }

}
Blaise Doughan