Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Null Pointer Exception in JAXB RI ClassFactory

Intro

My friend and I are working on a JavaFX application that acts as a planner for our school. We have tasks (homework for classes), events, courses and student info. In an attempt to store data persistently on the user's hard drive we are using JAXB.

We have annotated our classes and can successfully marshall the Task class in a wrapper. The problem is unmarshalling from the tasks.xml file.

Here are the relevant lines of code:

Task.java

@XmlRootElement
public class Task {
    //constructors

    //complete constructor
    public Task(String className, String assignment, String description, LocalDate dueDate) {
        this.className = new SimpleStringProperty(className);
        this.assignment = new SimpleStringProperty(assignment);
        this.description = new SimpleStringProperty(description);

        this.dueDate = new SimpleObjectProperty<LocalDate>(dueDate);
    }

    /**
     * Sets a model data into the task, sets the 
     * due date to be tomorrow.
     */
    public Task() {
        this("", "", "", LocalDate.now().plusDays(1));

        setClassName("English");
        setAssignment("Read");
        setDescription("1984");

        //setDueDate(LocalDate.now());
    }
    //Instance variables

    private final SimpleStringProperty className;
    private final SimpleStringProperty assignment;
    private final SimpleStringProperty description;

    private final ObjectProperty<LocalDate> dueDate;

//  //Getters and setters

    //... Other getters and setters

    @XmlJavaTypeAdapter(LocalDateAdapter.class)
    public final java.time.LocalDate getDueDate() {
        return this.dueDateProperty().get();
    }
    public final void setDueDate(final java.time.LocalDate dueDate) {
        this.dueDateProperty().set(dueDate);
    }
}

TaskListWrapper.java:

    //used in saving the objects to XML

@XmlRootElement(name="tasks")
public class TaskListWrapper {

        private ObservableList<Task> task;

        @XmlElement(name="task")
        public ObservableList<Task> getTasks() {
            return task;
        }

        public void setTasks(ObservableList<Task> tasks) {
            this.task = tasks;
        }

}

Method in AppData.java

It deals with saving to and unmarshalling from files.

/**
     * Save to XML using JAXB
     * @throws JAXBException 
     * @throws FileNotFoundException 
     */
    public static void save() throws JAXBException, FileNotFoundException {

        //saving other objects
        //...

        TaskListWrapper tl = new TaskListWrapper();

        //MasterTaskList is the entire list of tasks written to memory
        tl.setTasks(AppData.getMasterTaskList());

        saveObject(tl, new File(System.getProperty("user.dir") + "/resources/xml/tasks.xml"));

        saveObject(masterStudentInfo, new File(System.getProperty("user.dir") + "/resources/xml/student_info.xml"));
    }

saveObject() method in the same class:

/**
     * Saves a specific Object {@code obj} to an xml file {@code xml} using JAXB.
     * @param obj
     * @param xml
     * @throws FileNotFoundException
     * @throws JAXBException
     */
    private static void saveObject(Object obj, File xml) throws FileNotFoundException, JAXBException {
        //context is used to determine what kind of class is going to be marshalled or unmarshalled
        JAXBContext context = JAXBContext.newInstance(obj.getClass());

        //loads to the XML file
        Marshaller m = context.createMarshaller();
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);

        //loads the current list of courses to the courses.xml file
        m.marshal(obj, new FileOutputStream(xml));
    }

InitFiles() in App.java

Note the Comment pointing out the null pointer exception

/**
     * Initial setup for all the files for the program. Contains all the
     * persistent data for the planner, such as courses, tasks, and events.
     * <p>
     * All data is saved in {@code [place of installment]/resources/xml/...}.
     * @throws IOException
     */
    public void initFiles() throws IOException{

        //... other files for other objects

        File tasks = new File(System.getProperty("user.dir") + "/resources/xml/tasks.xml");

        //check if each file exists, if so unmarshall 
        if(tasks.exists()){
            try {
                JAXBContext context = JAXBContext.newInstance(TaskListWrapper.class);

                //the file location is correct
                System.out.println(tasks.toString());

                //The context knows that both the Task and TaskListWrapper classes exist
                System.out.println(context.toString());

                Unmarshaller um = context.createUnmarshaller();

                //TODO: null pointer exception
                TaskListWrapper taskList = (TaskListWrapper) um.unmarshal(tasks);
                //System.out.println(umObject.getClass());
            } catch (JAXBException e) {
                e.printStackTrace();
            }

        } else {
            tasks.createNewFile();
        }
        //... other checks for files
    }

Well-Formed XML document from the marshalling:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<tasks>
    <task>
        <assignment>Book</assignment>
        <className>Math</className>
        <description>problems</description>
        <dueDate>2015-01-17</dueDate>
    </task>
    <task>
        <assignment>Textbook</assignment>
        <className>Religion</className>
        <description>problems</description>
        <dueDate>2015-01-17</dueDate>
    </task>
    <task>
        <assignment>Read</assignment>
        <className>English</className>
        <description>1984</description>
        <dueDate>2015-03-05</dueDate>
    </task>
</tasks>

The exception:

 java.lang.NullPointerException
    at com.sun.xml.internal.bind.v2.ClassFactory.create0(Unknown Source)
    at com.sun.xml.internal.bind.v2.ClassFactory.create(Unknown Source)
    at com.sun.xml.internal.bind.v2.runtime.reflect.Lister$CollectionLister.startPacking(Unknown Source)
    at com.sun.xml.internal.bind.v2.runtime.reflect.Lister$CollectionLister.startPacking(Unknown Source)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.Scope.add(Unknown Source)
    at com.sun.xml.internal.bind.v2.runtime.property.ArrayERProperty$ReceiverImpl.receive(Unknown Source)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.endElement(Unknown Source)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.SAXConnector.endElement(Unknown Source)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(Unknown Source)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(Unknown Source)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(Unknown Source)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(Unknown Source)
    at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(Unknown Source)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)
    at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(Unknown Source)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(Unknown Source)
    at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(Unknown Source)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(Unknown Source)
    at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(Unknown Source)
    at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(Unknown Source)
    at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(Unknown Source)
    at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(Unknown Source)
    at org.sjcadets.planner.App.initFiles(App.java:136)
    at org.sjcadets.planner.App.start(App.java:68)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$153(Unknown Source)
    at com.sun.javafx.application.LauncherImpl$$Lambda$51/1390460753.run(Unknown Source)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$166(Unknown Source)
    at com.sun.javafx.application.PlatformImpl$$Lambda$45/1051754451.run(Unknown Source)
    at com.sun.javafx.application.PlatformImpl.lambda$null$164(Unknown Source)
    at com.sun.javafx.application.PlatformImpl$$Lambda$47/231444107.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$165(Unknown Source)
    at com.sun.javafx.application.PlatformImpl$$Lambda$46/1775282465.run(Unknown Source)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(Unknown Source)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$141(Unknown Source)
    at com.sun.glass.ui.win.WinApplication$$Lambda$37/1109371569.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)

The null pointer is at the //TODO stated in the initFiles() method:

JAXBContext context = JAXBContext.newInstance(TaskListWrapper.class);

                    //the file location is correct
                    System.out.println(tasks.toString());

                    //The context knows that both the Task and TaskListWrapper classes exist
                    System.out.println(context.toString());

                    Unmarshaller um = context.createUnmarshaller();

                    //TODO: null pointer exception
                    TaskListWrapper taskList = (TaskListWrapper) um.unmarshal(tasks);

Things we have tried:

  • Messing with the names and annotations. It doesn't seem like naming is the issue.
  • Sysouting the File location to make sure it is correct.
  • Sysouting the classes that the JAXBContext knows. It recognizes both the Task and TaskListWrapper classes.
  • Sysouting um.toString(). It shows a valid address in memory, so the um object itself is not what is throwing the nullpointer exception.
  • Changing the location of TaskListWrapper.java to the same package as Task.java.
  • Trying to unmarshal a single Task by changing the XML file to have only one <task> as the root element works when I change

    TaskListWrapper taskList = (TaskListWrapper) um.unmarshal(tasks);
    

    to

    Task taskList = (Task) um.unmarshal(tasks);
    

Places we have looked for answers:

  • http://examples.javacodegeeks.com/core-java/xml/bind/jaxb-unmarshal-example/
  • A multitude of stackoverflow questions which had to do with bug with the @XMLAttribute annotation. Since we don't use those that bug is not relevant
  • Learning Java: 4th Edition by Patrick Niemeyer and Daniel Leuck. We have copied their exact way of setting up the unmarshaller. They have a simple approach:

    JAXBContext context = JAXBContext.newInstance(Inventory.class);
    Unmarshaller unmarshaller = context.createUnmarshaller();
    Inventory inventory = (Inventory) unmarshaller.unmarshall(
        new File("zooinventory.xml") );
    

Question

Why is TaskListWrapper taskList = (TaskListWrapper) um.unmarshal(tasks); throwing a null pointer exception?

like image 313
Tom Magnusson Avatar asked Mar 08 '15 17:03

Tom Magnusson


1 Answers

JAXB isn't compatible to FXCollections like the ObservableList in your wrapper. You have to write an XmlAdapter to untangle it to a normal List. So marshalling will function but unmarshalling not, as you can see in the line of the stacktrace:

at com.sun.xml.internal.bind.v2.runtime.reflect.Lister$CollectionLister.startPacking(Unknown Source)

There is the Lister$CollectionLister which don't know what to do with the Unknown Source. So an Adpater should use a ListWrapper like this:

public class TaskList {

  @XmlElement(name = "task")
  List<Task> entries = new ArrayList<>();

  public List<Task> getEntries() {
    return entries;
  }
}

The corresponding Adapter look like this:

public class TaskListAdapter extends XmlAdapter<TaskList, ObservableList<Task>> {

  @Override
  public ObservableList<Task> unmarshal(TaskList v) throws Exception {
    ObservableList<Task> list = FXCollections.observableArrayList(v.entries);
    return list;
  }

  @Override
  public TaskList marshal(ObservableList<Task> v) throws Exception {
    TaskList taskList = new TaskList();
    v.stream().forEach((item) -> {
      taskList.entries.add(item);
    });
    return taskList;
  }
}

So that your TaskListWrapper should finaly look like this:

//used in saving the objects to XML

@XmlRootElement(name="tasks")
public class TaskListWrapper {

        private ObservableList<Task> task;


        @XmlJavaTypeAdapter(TaskListAdapter.class)
        public ObservableList<Task> getTasks() {
            return task;
        }

        public void setTasks(ObservableList<Task> tasks) {
            this.task = tasks;
        }

}

And by the way, there are a lot of FX Properties you use, so maybe you better annotate your class Task with @XmlAccessorType(XmlAccessType.PROPERTY) and make sure, that for every field to be set a getter/setter exist. Like the FXProperties convention says:

private final StringProperty description = new SimpleStringProperty();
public String getDescription() {
  return description.get();
}
public void setDescription(String description) {
  this.description.set(description);
}
public StringProperty descriptionProperty(){
  return description;
}

The Annotation @XmlAccessType(XmlAccessorType.PROPERTY) is described in detail here: JAXB JavaDoc. If on a package or class nothing is annotated, than the default will be @XmlAccessorType(XmlAccessType.PUBLIC_MEMBER), where the JavaDoc says:

Every public getter/setter pair and every public field will be automatically bound to XML, unless annotated by XmlTransient.

So in a FX class (a model in special) you try to hide the used properties in private fields. But what if you need a public field that should not be marshalled? Then I recommend doing the @XmlAccessorType(XmlAccessType.PROPERTY) annotation. The JavaDoc of it says:

Every getter/setter pair in a JAXB-bound class will be automatically bound to XML, unless annotated by XmlTransient.

Watch at the little difference in one word public, so if annotated with @XmlAccessorType(XmlAccessType.PROPERTY) even private getter/setter will be taken into account.

But I think most of the people use @XmlAccessorType(XmlAccessType.FIELD) where the JavaDoc says:

Every non static, non transient field in a JAXB-bound class will be automatically bound to XML, unless annotated by XmlTransient.

This can be a bit tricky in an FX class with FX properties. I wouldn't recommend it to you.

like image 175
aw-think Avatar answered Oct 09 '22 10:10

aw-think