Reputation: 1378
I'd like to marshall/unmarshall an object containing an EnumSet field. EnumSet has no constructors so I'm trying to write an adapter for it. Of course, it should work for any EnumType so I'm trying to use generics but I don't know the exact EnumType during unmarshalling. Am I doing it the wrong way? Is there some straightforward solution?
intended usage:
@XmlJavaTypeAdapter(EnumSetAdapter.class)
private final EnumSet<MyEnumType> status;
and my attempt looks like:
import java.util.EnumSet;
import java.util.stream.Collectors;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class EnumSetAdapter<E extends Enum<E>> extends XmlAdapter<String, EnumSet<E>> {
private static final String DELIMITER = ",";
@Override
public EnumSet<E> unmarshal(String v) throws Exception {
if (v == null) {
return null;
}
String[] enumNameArr = v.split(DELIMITER);
//how to get the actual EnumType here?
List<E> enumList = Stream.of(enumNameArr).map(str -> E.valueOf(!!! enumType !!!, str))...;
EnumSet<E> resultEnumSet = EnumSet.copyOf(enumList);
return resultEnumSet;
}
@Override
public String marshal(EnumSet<E> v) throws Exception {
return (v == null) ? null : v.stream().map(e -> e.name()).collect(Collectors.joining(DELIMITER));
}
}
Thanks for any help!
Upvotes: 1
Views: 51
Reputation: 206876
Here is a partial answer...
The reason you cannot do something like E.valueOf(...)
is because E
is not a concrete type; it's just a type variable, and because of type erasure, at runtime it's not known what the actual type is that E
stands for.
You would need the Class<E>
object that specifies the type of the actual enum, but the problem is that it's not easy to get this in an XmlAdapter
. For example like this:
public class EnumSetAdapter<E extends Enum<E>> extends XmlAdapter<String, EnumSet<E>> {
private static final String DELIMITER = ",";
private final Class<E> enumClass;
public EnumSetAdapter(Class<E> enumClass) {
this.enumClass = enumClass;
}
@Override
public EnumSet<E> unmarshal(String v) throws Exception {
if (v == null) {
return null;
}
String[] enumNameArr = v.split(DELIMITER);
List<E> enumList = Stream.of(enumNameArr).map(str -> Enum.valueOf(enumClass, str)).collect(Collectors.toList());
return EnumSet.copyOf(enumList);
}
@Override
public String marshal(EnumSet<E> v) throws Exception {
return (v == null) ? null : v.stream().map(Enum::name).collect(Collectors.joining(DELIMITER));
}
}
Problem: JAXB is going to instantiate your EnumSetAdapter
which does not give you the chance to pass the constructor parameter. So using @XmlJavaTypeAdapter(EnumSetAdapter.class)
won't work.
Possible solutions: See Anyway to pass a constructor parameter to a JAXB Adapter?
One possible, but clunky solution would be to create an adapter for each specific type of enum, that extends EnumSetAdapter
and that passes the appropriate value to its superclass constructor. For example:
public class MyEnumTypeAdapter extends EnumSetAdapter<MyEnumType> {
public MyEnumTypeAdapter() {
super(MyEnumType.class);
}
}
And then:
@XmlJavaTypeAdapter(MyEnumTypeAdapter.class)
private final EnumSet<MyEnumType> status;
Disadvantage: You would need a class for each enum type.
See the question I linked to above for other possible solutions.
Upvotes: 3