views:

4130

answers:

5

I am trying to use JAXB's introspection to marshall and unmashall some existing domain objects marked up with JAXB annotations. Most things work as expected, but I am having quite a bit of trouble getting a fairly simple class to serialize. This class is used as an @XmlElement on a number of beans and looks something like:

public class Range<E extends Comparable<E>> implements Serializable {
    protected boolean startInclusive, endInclusive;
    protected E       start, end;

    public Range(){
            startInclusive = endInclusive = true;
    }

    public boolean contains(E value){...}

    public E getEnd() {
            return end;
    }

    public void setEnd(E end) {
            this.end = end;
    }

    public boolean isEndInclusive() {
            return endInclusive;
    }

    public void setEndInclusive(boolean endInclusive) {
            this.endInclusive = endInclusive;
    }

    public E getStart() {
            return start;
    }

    public void setStart(E start) {
            this.start = start;
    }

    public boolean isStartInclusive() {
            return startInclusive;
    }

    public void setStartInclusive(boolean startInclusive) {
            this.startInclusive = startInclusive;
    }
}

I have tried to do the following, with no success, JAXB is still angry with the interface Comparable.

public class DoubleRange extends Range<Double> {}

Using both Range and DoubleRange as return types for the bean getter's yields an exception like:

java.lang.Comparable is an interface, and JAXB can't handle interfaces.
    this problem is related to the following location:
     at java.lang.Comparable
     at protected java.lang.Comparable com.controlpath.util.Range.start
     at example.util.Range
     at example.util.DoubleRange
     at public example.util.DoubleRange example.domain.SomeBean.getRange()
     at example.domain.SomeBean

I realize that in most cases List<T> and Map<T, U> only work because the JAXB specification has special provisions for those types when they are encountered on beans, but is there any way to convey what I want to the JAXB introspection engine without having to reimplement range with non-generic fields?

A: 

Try something like Simple XML Serialization it comes with support for generic types in XML elements with a number of annotations such as @Element and @ElementList. The programming model is very similar, but simpler than JAXB.

ng
A: 

So it looks like the problem is the erasure of E on start and end is Comparable. If it can't handle interfaces you could try Object, but I would hope it complains at that too (either now or later). Possibly you could make Range abstract and specialise it for each specific E. I should know more about JAXB.

Tom Hawtin - tackline
Am I incorrect in assuming that the DoubleRange class would solve the erasure problem? I tried redefining the getters as public Double getEnd() and public Double getStart() but I got the same error. It seems like it is looking too deeply for the types.
Ambience
IIRC, JAXB can either look at the getters and setter or (better) the fields. If the latter then you'd need to move the field up to the specialised class, and make getStart/getEnd abstract. Possibly the abstract class shouldn't be annotated as JAXB encodeable. I'm not really an expert in this area as you might be able to tell...
Tom Hawtin - tackline
+3  A: 

You could write a custom adapter (not using JAXB's XmlAdapter) by doing the following:

1) declare a class which accepts all kinds of elements and has JAXB annotations and handles them as you wish (in my example I convert everything to String)

@YourJAXBAnnotationsGoHere
public class MyAdapter{

  @XmlElement // or @XmlAttribute if you wish
  private String content;

  public MyAdapter(Object input){
    if(input instanceof String){
      content = (String)input;
    }else if(input instanceof YourFavoriteClass){
      content = ((YourFavoriteClass)input).convertSomehowToString();
    }else if(input instanceof .....){
      content = ((.....)input).convertSomehowToString();
    // and so on
    }else{
      content = input.toString();
    }
  }
}

// I would suggest to use a Map<Class<?>,IMyObjToStringConverter> ...
// to avoid nasty if-else-instanceof things

2) use this class instead of E in your to-be-marshalled class

NOTES

  • Of course this would not work for complex (nested) data structures.
  • You have to think how to unmarshall this back again, could be more tricky. If it's too tricky, wait for a better proposal than mine ;)
ivan_ivanovich_ivanoff
I am trying to get some insight as to why JAXB is upset and avoid a lot of custom marshalling code (as I am sure I will hit this again at some point in the future, potentially on a much more complex object). But this technically IS an answer, and I will mark it as such in a few days if no one else has a better one. Thanks =)
Ambience
+1  A: 

And, how about

public class Range<E extends Number> implements Serializable { ...

  • Number is a class

  • I bet JAXB knows default marshalling/unmarshalling rules for Number

For unmarshalling to specific type, you need XmlAdapter as I described here

ivan_ivanovich_ivanoff
Good suggestion, unfortunately JAXB tries to instantiate java.lang.Number when unmarshalling >.< And Number is not Comparable, but I could have gotten around that with some casting.
Ambience
Than you need XmlAdapter, see edited answer
ivan_ivanovich_ivanoff
A: 

Actually, it is not quite clear to me why this would not work. It seems like JAXB should be able to resolve specific subtype correctly: if (and only if!) this type is NOT the root type (which it is not as per your description). I mean, it is just a Bean; so if bean with T replaced with direct type works, so should generic version iff using sub-classing to bind types (as is done in example).

So perhaps it could be a bug in implementation?

StaxMan