简体   繁体   中英

How to make JAXB instantiate subclass list element of generic super class

I'm trying to umarshall multiple occurrences of one subclass ChildListElement of a generic super class ParentListElement into a list using JAXB. The problem is that JAXB is unmarshalling to the superclass instead of the subclass. How do I get JAXB to unmarshall to ChildListElement instead of ParentListElement without locking in the one subclass ChildListElement?

Thanks for your time and assistance. :-)

These are the files/classes:

main.java
ParentChildTests.java
ParentChildFactory.java
childBucket.xml
ParentBucket.java
ChildBucket.java
ParentListElement.java
ChildListElement.java

Using wrapper and elements annotations in ParentBucket only works if type=ChildListElement.class is specified. But that defeats the purpose of having a generic super class because the one subclass ChildListElement is locked in. I want to be able to have multiple subclasses of ParentListElement. Each list will hold only one kind of subclass at a time.

Works - unmarshalls to ChildListElement BUT LOCKS TO ONE SUBCLASS:

@XmlElementWrapper( name = "elements" )
@XmlElements( { @XmlElement( name="element", type=ChildListElement.class ) } )

Does not work - unmarshalls to ParentListElement:

@XmlElementWrapper( name = "elements" )
@XmlElements( { @XmlElement( name="element" ) } )

main.java:

import GenListVsJaxbTests.ParentChildTests;
import javax.xml.bind.JAXBException;

public class Main
{
    public static void main(String[] args) throws JAXBException
    {
        ParentChildTests.testChildBucket();
        ParentChildTests.testChildBucketFromXml();
    }
}

ParentChildTests.java:

package GenListVsJaxbTests;
import javax.xml.bind.JAXBException;

public class ParentChildTests
{
    public static void testChildBucket()
    {
        ChildBucket bucket = ParentChildFactory.getNewChildBucket();
        bucket.test();
    }

    public static void testChildBucketFromXml() throws JAXBException
    {
        ChildBucket bucket = ParentChildFactory.loadNewChildBucketFromXml ( "src/GenListVsJaxbTests/ChildBucket.xml" );
        bucket.test();
    }
}

ParentChildFactory.java:

package GenListVsJaxbTests;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class ParentChildFactory
{
    private static int MAX_ELEMENTS = 3;

    public static ChildBucket getNewChildBucket()
    {
        ChildBucket childBucket = new ChildBucket();
        List<ChildListElement> list = new ArrayList<>();

        for ( int i = 0; i < MAX_ELEMENTS; i ++ )
        {
            ChildListElement el = new ChildListElement();
            el.setParentListElMember ( String.valueOf ( i ) );
            el.setChildListElMember ( String.valueOf ( i + 10 ) );
            list.add ( el );
        }
        childBucket.setElementList ( list );
        return childBucket;
    }

    public static ChildBucket loadNewChildBucketFromXml ( String fileName ) throws JAXBException
    {
        File inFile = new File( fileName );
        JAXBContext jaxbContext = JAXBContext.newInstance ( ChildBucket.class );
        Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
        ChildBucket bucket = (ChildBucket) jaxbUnmarshaller.unmarshal(inFile);
        return bucket;
    }
}

childBucket.xml:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<childBucket
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation='ChildBucket.xsd'
        >
    <childBucketMember>child bucket</childBucketMember>
    <parentBucketMember>parent bucket</parentBucketMember>
    <elements>
        <element>
            <childListElMember>child element 1</childListElMember>
            <parentListElMember>parent element 1</parentListElMember>
        </element>
        <element>
            <childListElMember>child element 2</childListElMember>
            <parentListElMember>parent element 2</parentListElMember>
        </element>
        <element>
            <childListElMember>child element 3</childListElMember>
            <parentListElMember>parent element 3</parentListElMember>
        </element>
        <element>
            <childListElMember>child element 4</childListElMember>
            <parentListElMember>parent element 4</parentListElMember>
        </element>
    </elements>
</childBucket>

ParentBucket.java:

package GenListVsJaxbTests;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlElements;
import java.util.List;

public class ParentBucket<LE extends ParentListElement>
{
    protected String parentBucketMember = "parentBucketMember";
    List<LE> elementList;


    public String getParentBucketMember()
    {
        return parentBucketMember;
    }

    public void setParentBucketMember(String parentBucketMember)
    {
        this.parentBucketMember = parentBucketMember;
    }

    public List<LE> getElementList()
    {
        return elementList;
    }

    @XmlElementWrapper( name = "elements" )
    @XmlElements( { @XmlElement( name="element" ) } )
//    @XmlElements( { @XmlElement( name="element", type=ChildListElement.class ) } )
    public void setElementList(List<LE> elementList)
    {
        this.elementList = elementList;
    }

    public void test()
    {
        System.out.println("ParentBucket.test");
        System.out.println("parentBucketMember: " + parentBucketMember);

        for ( LE el : elementList  )
        {
            el.test();
        }
    }
}

ChildBucket.java:

package GenListVsJaxbTests;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class ChildBucket extends ParentBucket<ChildListElement>
{
    protected String childBucketMember = "childBucketMember";

    public String getChildBucketMember()
    {
        return childBucketMember;
    }

    public void setChildBucketMember(String childBucketMember)
    {
        this.childBucketMember = childBucketMember;
    }

    public void test()
    {
        System.out.println("ChildBucket.test");
        System.out.println("childBucketMember: " + childBucketMember);
        super.test();
        System.out.println("---");

        for ( ChildListElement el : elementList  )
        {
            el.test();
        }
        System.out.println("===");
    }
}

ParentListElement.java:

package GenListVsJaxbTests;

public class ParentListElement
{
    protected String parentListElMember = "parentListElMember";

    public String getParentListElMember()
    {
        return parentListElMember;
    }

    public void setParentListElMember(String parentListElMember)
    {
        this.parentListElMember = parentListElMember;
    }

    public void test()
    {
        System.out.println("ParentListElement.test");
        System.out.println("parentListElMember: " + parentListElMember);
    }
}

ChildListElement.java:

package GenListVsJaxbTests;

public class ChildListElement extends ParentListElement
{
    protected String childListElMember = "childListElMember";

    public String getChildListElMember()
    {
        return childListElMember;
    }

    public void setChildListElMember(String childListElMember)
    {
        this.childListElMember = childListElMember;
    }

    @Override
    public void test()
    {
        super.test();
        System.out.println("ChildListElement.test");
        System.out.println("childListElMember: " + childListElMember);
    }
}

A subsequent for loop throws this error during runtime (abbreviated):

ClassCastException: ParentListElement cannot be cast to ChildListElement

When marshalling correctly, output looks like this:

"C:\Program Files\Java\jdk1.8.0_211\bin\java" -Didea.launcher.port=7538 "-Didea.launcher.bin.path=[SNIP] com.intellij.rt.execution.application.AppMain Main
ChildBucket.test
childBucketMember: childBucketMember
ParentBucket.test
parentBucketMember: parentBucketMember
ParentListElement.test
parentListElMember: 0
ChildListElement.test
childListElMember: 10
ParentListElement.test
parentListElMember: 1
ChildListElement.test
childListElMember: 11
ParentListElement.test
parentListElMember: 2
ChildListElement.test
childListElMember: 12
---
ParentListElement.test
parentListElMember: 0
ChildListElement.test
childListElMember: 10
ParentListElement.test
parentListElMember: 1
ChildListElement.test
childListElMember: 11
ParentListElement.test
parentListElMember: 2
ChildListElement.test
childListElMember: 12
===
ChildBucket.test
childBucketMember: child bucket
ParentBucket.test
parentBucketMember: parent bucket
ParentListElement.test
parentListElMember: parent element 1
ChildListElement.test
childListElMember: child element 1
ParentListElement.test
parentListElMember: parent element 2
ChildListElement.test
childListElMember: child element 2
ParentListElement.test
parentListElMember: parent element 3
ChildListElement.test
childListElMember: child element 3
ParentListElement.test
parentListElMember: parent element 4
ChildListElement.test
childListElMember: child element 4
---
ParentListElement.test
parentListElMember: parent element 1
ChildListElement.test
childListElMember: child element 1
ParentListElement.test
parentListElMember: parent element 2
ChildListElement.test
childListElMember: child element 2
ParentListElement.test
parentListElMember: parent element 3
ChildListElement.test
childListElMember: child element 3
ParentListElement.test
parentListElMember: parent element 4
ChildListElement.test
childListElMember: child element 4
===

Process finished with exit code 0

When marshalling incorrectly, output looks like this:

"C:\Program Files\Java\jdk1.8.0_211\bin\java" -Didea.launcher.port=7542 "-Didea.launcher.bin.path=[SNIP] com.intellij.rt.execution.application.AppMain Main
ChildBucket.test
childBucketMember: childBucketMember
ParentBucket.test
parentBucketMember: parentBucketMember
ParentListElement.test
parentListElMember: 0
ChildListElement.test
childListElMember: 10
ParentListElement.test
parentListElMember: 1
ChildListElement.test
childListElMember: 11
ParentListElement.test
parentListElMember: 2
ChildListElement.test
childListElMember: 12
---
ParentListElement.test
parentListElMember: 0
ChildListElement.test
childListElMember: 10
ParentListElement.test
parentListElMember: 1
ChildListElement.test
childListElMember: 11
ParentListElement.test
parentListElMember: 2
ChildListElement.test
childListElMember: 12
===
ChildBucket.test
childBucketMember: child bucket
ParentBucket.test
parentBucketMember: parent bucket
ParentListElement.test
parentListElMember: parent element 1
ParentListElement.test
parentListElMember: parent element 2
ParentListElement.test
parentListElMember: parent element 3
ParentListElement.test
parentListElMember: parent element 4
---
Exception in thread "main" java.lang.ClassCastException: GenListVsJaxbTests.ParentListElement cannot be cast to GenListVsJaxbTests.ChildListElement
    at GenListVsJaxbTests.ChildBucket.test(ChildBucket.java:27)
    at GenListVsJaxbTests.ParentChildTests.testChildBucketFromXml(ParentChildTests.java:17)
    at com.caci.irma.experiment.Main.main(Main.java:14)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

Process finished with exit code 1

This question below is similar to mine:

Marshalling/Unmarshalling Java superclass and subclasses using JAXB

With the @XmlElements annotation, you can specify multiple @XmlElement/class entries. This means that you can have different destination classes for different @XmlElement names.

@XmlElementWrapper( name = "elements" )
@XmlElements
( {
    @XmlElement( name="el1", type=ChildListElement1.class ),
    @XmlElement( name="el2", type=ChildListElement2.class )
} )

However, this still means that the superclass must know about the subclasses. Every time you create a subclass, the superclass needs to be modified too in order for the subclass to be a JAXB destination class. Seems a bit tightly coupled. Also, each class needs its own unique element name. This makes each XML file need its own schema, or perhaps a schema can be shared amongst them all if it is more complicated and allows different element names for the same structure.

I read various posts about IDResolver.bind, jaxb:bindings, SchemaFactory, AnnotationHelper, StreamReaderDelegate, etc. The jaxb:bindings looked promising, seeming like it could possibly allow specifying a destination class for a given node name. However, the bindings in the XSD schema were actually ignored during unmarshalling.

Another way of potentially handling the issue was to:

  1. In the superclass, make the setter/getter abstract.
  2. Move the implemented setters/getters from the superclass to the subclasses.
  3. Annotate the setters/getters in the subclasses.
  4. Do not need to specify the subclass in the @XmlElement annotation.

Superclass:

public abstract class ParentBucket<LE extends ParentListElement>
{
    List<LE> elementList;

    public abstract void setElementList(List<LE> elementList);
    public abstract List<LE> getElementList();
}

Subclass 1:

@XmlRootElement
public class ChildBucket1 extends ParentBucket<ChildListElement1>
{
    @Override
    @XmlElementWrapper( name = "elements" )
    @XmlElements ( { @XmlElement( name="element" ) } )
//    @XmlElements ( { @XmlElement( name="element", type=ChildListElement1.class ) } )
    public void setElementList(List<ChildListElement1> elementList)
    {
        super.setElementListCore ( elementList );
    }
    @Override
    public List<ChildListElement1> getElementList()
    {
        return super.getElementListCore();
    }
}

Subclass 2:

@XmlRootElement
public class ChildBucket2 extends ParentBucket<ChildListElement2>
{
    @Override
    @XmlElementWrapper( name = "elements" )
    @XmlElements ( { @XmlElement( name="element" ) } )
//    @XmlElements ( { @XmlElement( name="element", type=ChildListElement2.class ) } )
    public void setElementList(List<ChildListElement2> elementList)
    {
        super.setElementListCore ( elementList );
    }
    @Override
    public List<ChildListElement2> getElementList()
    {
        return super.getElementListCore();
    }
}

Originally, it worked in my experimental code, but it did not work in the "real" code. Took a while to figure out why it worked in one project and not the other. Almost gave up to annotate the superclass and move on.

HOWEVER :-) I figured out the problem. Annotating the subclass, if the Java member list name is "fields", and the getter/setter are named getFields/setFields, then JAXB cannot "find" the subclass and tries to instantiate the superclass instead. JAXB will not warn of any conflict. The runtime fails when it tries to instantiate an abstract class. Or if the superclass is not abstract, later "for" loops with concrete subclasses will fail with ClassCastException's. By changing the name of the list variable from "fields" to "listOfFields", and the corresponding abstract and implemented getter/setter, it worked!

Problem superclass:

public abstract class ParentBucketOops<FE extends FieldElement>
{
    List<FE> fields; // oops, JAXB has a runtime problem later

    public abstract void setFields(List<FE> fields);
    public abstract List<FE> getFields();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM