Below (at the end of this question) is a generic JAXB marshaller/unmarshaller that uses Java generics. It works. It can marshal/unmarshal JAXB objects with one line:
JAXBUtils jaxbUtils = new JAXBUtils(ObjectFactory.class);
SomeClass object = jaxbUtils.unmarshall("<some xml >...");
String xml = jaxbUtils.marshall(object);
I also needed to clone some of these objects, so I used marshall and unmarshall to deep clone any JAXB object. This also works without any compiler errors (just some warnings suppressed with annotations).
SomeClass object = ... some JAXB object obtained from XML
SomeClass cloneObject = clone(object);
But in another environment (continuous integration) I am getting a compiler error in the clone method return line:
type parameters of <T>T cannot be determined; no unique maximal instance exists for type variable T with upper bounds T,java.lang.Object
return unmarshal(marshal(object));
^
I downgraded my local environment from 1.6.0_27 to 1.6.0_23, so I have exactly the same version used in CI server, as I found some bugs related to generic code were fixed between those versions:
java version "1.6.0_23" Java(TM) SE Runtime Environment (build 1.6.0_23-b05)
But the code is still working for me, no compiler errors, unit tests passed, everything works.
I tried changing clone method to:
public <T> T clone(T object) throws UnsupportedEncodingException, JAXBException {
return this.<T>unmarshal(this.<T>marshal(object));
}
That also compiles for me and looks as it gives more hints to the compiler, but I'm not sure if it will solve the problem in the server since I am not able to reproduce the error.
Do you see any problem with the use of generic code in clone method? Is it a bug in the compiler or is my code incorrect? And if it is incorrect why it works for me !?
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import org.xml.sax.InputSource;
public class JAXBUtils {
private Marshaller marshaller;
private Unmarshaller unmarshaller;
public JAXBUtils(Class... classesToBeBound) throws JAXBException {
final JAXBContext context = JAXBContext.newInstance(classesToBeBound);
unmarshaller = context.createUnmarshaller();
marshaller = context.createMarshaller();
}
@SuppressWarnings("unchecked")
public <T> String marshal(T object) throws UnsupportedEncodingException, JAXBException {
return marshal(object, object.getClass().getSimpleName(), (Class<T>) object.getClass());
}
public <T> String marshal(T object, String name, Class<T> clazz) throws JAXBException {
final JAXBElement<T> jaxbElement = new JAXBElement<T>(new QName(name), clazz, object);
final OutputStream os = new ByteArrayOutputStream();
marshaller.marshal(jaxbElement, os);
return os.toString();
}
@SuppressWarnings("unchecked")
public <T> T unmarshal(String xml) throws UnsupportedEncodingException, JAXBException {
final InputSource is = new InputSource(new ByteArrayInputStream(xml.getBytes("utf-8")));
final Object object = unmarshaller.unmarshal(is);
// JAXB sometimes returns directly the object and sometimes wraps it in a JAXBElement.
// Not sure why just handling both cases.
return object instanceof JAXBElement ? ((JAXBElement<T>) object).getValue() : (T) object;
}
public <T> T clone(T object) throws UnsupportedEncodingException, JAXBException {
return unmarshal(marshal(object));
}
}
UPDATE: the server where it's giving the compiler error is a RedHat Linux (5 or 6 not sure). As I said the java version is the same, so I guess the bug is fixed for windows but not for linux. I will try to access some linux machine to try.
UPDATE: the code compiles without any error or warning in debian/open-jdk and ubuntu/sun-jdk6u23. I haven't been able to test on RedHat/sun-jdk6u23.