0

I am learning JavaFX, and have hit a stumbling block with running a task. I have followed the progress of the task with the NetBeans debugger, and found that ConfigModelSansUpdateTask clearly runs.

When the application gets to the configModelWorkerSansUpdates.setOnSucceeded, somehow this result (stored in configModel) becomes null, throwing a NullPointerException.

Having read through the post on NullPointerException, I am none the wiser as to why the result of my Task is being being nulled. There is nothing obvious that does this, as I clearly get a result that then becomes null when I come to use it:

Snapshot of when the task is about to return a result:

NetBeans

And when I come to use the result:

Null result

Help!

SWMUApp.java

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package swmuapp;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import jaxb.TaskModel;

/**
 *
 * @author Shaun Connelly-Flynn
 */

public class SWMUApp extends Application {

public SWMUApp() {
}

@Override
public void start(Stage stage) throws Exception {
    FXMLLoader centralSeceneLoader = new FXMLLoader(getClass().getResource("forms/CentralScene.fxml"));
    Parent centralRoot = (Parent) centralSeceneLoader.load();

    Scene scene = new Scene(centralRoot);

    stage.setScene(scene);
    stage.show();
}

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {
    launch(args);
}

}

CentralSceneController.java

package swmuapp.controllers;

import java.net.URL;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.concurrent.WorkerStateEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Tab;
import jaxb.TaskModel;
import swmuapp.SWMUApp;
import swmuapp.init.ConfigModelWorkerSansUpdates;
import swmuapp.init.LocationModelWorker;
import swmuapp.models.ConfigModel;
import swmuapp.models.LocationModel;

/**
 * FXML Controller class
 *
 * @author Shaun Connelly-Flynn
 */

public class CentralSceneController implements Initializable {

    @FXML
    private Tab sessionTab;
    @FXML
    private Tab tractionTab;
    @FXML
    private Tab locationTab;
    @FXML
    private Tab swmuTab;
    @FXML
    private Tab exportTab;

    private final TaskModel taskModel;

    // Models
    private ConfigModel configModel;
    private LocationModel locationModel;

    // Child Controllers
    @FXML
    private ImportPaneController importPaneController;

    @FXML
    private SWMUPaneController swmuPaneController;

    @FXML
    private SessionPaneController sessionPaneController;

    public CentralSceneController() {
        this.taskModel = new TaskModel();
    }

    /**
     * Initialises the controller class.
     *
     * @param url
     * @param rb
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // Give a reference of this controller to the child controllers
        importPaneController.setParentController(this);

        // Now subimt the configModel task
        ConfigModelWorkerSansUpdates configModelWorkerSansUpdates = new ConfigModelWorkerSansUpdates(taskModel);

        configModelWorkerSansUpdates.setOnSucceeded((WorkerStateEvent event) -> {
            System.out.println(event.getEventType().toString());

            // Get the location model
            Future<LocationModel> locationFuture = taskModel.submitTask(new LocationModelWorker(configModel, taskModel));

            try {
                locationModel = locationFuture.get();
            } catch (InterruptedException | ExecutionException ex) {
                Logger.getLogger(SWMUApp.class.getName()).log(Level.SEVERE, null, ex);
            }

            // Notify the controllers that we're ready
            sessionPaneController.setConfigModel(configModel);
            sessionPaneController.setLocationModel(locationModel);
        });

        configModelWorkerSansUpdates.setOnFailed(p -> System.out.println(p.toString()));
        configModelWorkerSansUpdates.setOnCancelled(p -> System.out.println(p.toString()));

        // Fetch the config model
        Future<ConfigModel> configFuture = taskModel.submitTask(configModelWorkerSansUpdates);

        try {
            configModel = configFuture.get();
        } catch (InterruptedException | ExecutionException ex) {
            Logger.getLogger(SWMUApp.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

TaskModel.java

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package jaxb;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javafx.concurrent.Task;

/**
 *
 * @author Shaun Connelly-Flynn
 */
public class TaskModel {
    private final ExecutorService service;

    public TaskModel() {
        this.service = Executors.newCachedThreadPool();
    }

    public Future submitTask(Callable task) {
        return service.submit(task);
    }

    public Future submitTask(Task task) {
        return service.submit(task);
    }

    public List<Future> submitTasks(List<Callable> tasks) {
        List<Future> futureList = new ArrayList<>();

        for(Callable task : tasks) {
            futureList.add(submitTask(task));
        }

        return futureList;
    }
}

ConfigModelWorkerSansUpdates.java

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package swmuapp.init;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import javafx.concurrent.Task;
import jaxb.Binder;
import jaxb.TaskModel;
import jaxb.bundles.QueryBundle;
import jaxb.bundles.ResultsBundle;
import jaxb.query.XQuery;
import swmuapp.config.jaxb.JaxbConfig;
import swmuapp.config.jaxb.JaxbDatabases;
import swmuapp.models.ConfigModel;

/**
 *
 * @author Shaun Connelly-Flynn
 */
public class ConfigModelWorkerSansUpdates extends Task<ConfigModel> {

    private final TaskModel taskModel;

    public ConfigModelWorkerSansUpdates(TaskModel taskModel) {
        this.taskModel = taskModel;
    }

    @Override
    protected ConfigModel call() throws Exception {        
        // Get the list of active databases
        QueryBundle databaseBundle = new QueryBundle(
                new XQuery("SWMUDB", "<databases>{databases/database}</databases>"),
                JaxbDatabases.class,
                "src/swmuapp/schema/DatabaseSchema.xsd");

        // Now read the configuration
        QueryBundle configBundle = new QueryBundle(
                new XQuery("SWMUDB", "configs/config"),
                JaxbConfig.class,
                "src/swmuapp/schema/ConfigSchema.xsd");

        // Submit the tasks
        Future<ResultsBundle> dbTask = taskModel.submitTask(new Binder(databaseBundle, taskModel));
        Future<ResultsBundle> configTask = taskModel.submitTask(new Binder(configBundle, taskModel));

        // Create empty objects in case of failure!
        JaxbDatabases dbResult = new JaxbDatabases();
        JaxbConfig configResult = new JaxbConfig();

        try {
            // Now fetch the list of databases
            dbResult = (JaxbDatabases) dbTask.get().getResult();

            // Fetch the configuration
            configResult = (JaxbConfig) configTask.get().getResult();
        } catch (ExecutionException ex) {
            setException(ex);
            throw ex;
        }

        // Now put it all together
        ConfigModel configModel = new ConfigModel(dbResult);

        // And give it back!
        return configModel;
    }
}

SessionPaneController.java

package swmuapp.controllers;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ListView;
import javafx.scene.control.TableView;
import swmuapp.models.ConfigModel;
import swmuapp.models.LocationModel;

/**
 * FXML Controller class
 *
 * @author Shaun Connelly-Flynn
 */
public class SessionPaneController implements Initializable {

    @FXML
    private ListView<String> dbListView;
    @FXML
    private TableView<?> detailedTableView;

    private CentralSceneController controller;

    private ConfigModel configModel;
    private LocationModel locationModel;

    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {

    }

    public void setConfigModel(ConfigModel configModel) {
        if (this.configModel == null) {
            this.configModel = configModel;
        } else {
            throw new IllegalArgumentException("SessionPaneController::setConfigModel -- config model already set!");
        }

        dbListView.setItems(configModel.getDatabases());
    }

    public void setLocationModel(LocationModel locationModel) {
        this.locationModel = locationModel;
    }

    protected void setParentController(CentralSceneController controller) {
        this.controller = controller;
    }
}

Stack:

java.util.concurrent.ExecutionException: java.lang.NullPointerException
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:192)
    at swmuapp.controllers.CentralSceneController.lambda$initialize$0(CentralSceneController.java:84)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.concurrent.EventHelper.fireEvent(EventHelper.java:219)
    at javafx.concurrent.Task.fireEvent(Task.java:1356)
    at javafx.concurrent.Task.setState(Task.java:723)
    at javafx.concurrent.Task$TaskCallable.lambda$call$500(Task.java:1434)
    at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException
    at swmuapp.init.LocationModelWorker.call(LocationModelWorker.java:35)
    at swmuapp.init.LocationModelWorker.call(LocationModelWorker.java:22)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    ... 1 more

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
    at swmuapp.controllers.SessionPaneController.setConfigModel(SessionPaneController.java:49)
    at swmuapp.controllers.CentralSceneController.lambda$initialize$0(CentralSceneController.java:90)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.concurrent.EventHelper.fireEvent(EventHelper.java:219)
    at javafx.concurrent.Task.fireEvent(Task.java:1356)
    at javafx.concurrent.Task.setState(Task.java:723)
    at javafx.concurrent.Task$TaskCallable.lambda$call$500(Task.java:1434)
    at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:745)
swshaun
  • 384
  • 1
  • 4
  • 12
  • 2
    Possible duplicate of [What is a NullPointerException, and how do I fix it?](http://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it) – Luke Joshua Park Dec 04 '16 at 07:45
  • Having followed through the execution of the code with a debugger, I am none the wiser. – swshaun Dec 04 '16 at 07:50

2 Answers2

1

I suspect some combination of scheduling decisions lead to the onSucceeded handler to be executed before Future.get returns and therefore CentralSceneController.configModel has not yet been assigned.

Using Future.get() directly after posting the task from the same thread is a no-go anyways, since it forces the current thread to wait for the task to complete and could have used this time to run the ask directly. It's even worse if the current thread that should not be blocked, like the javafx application thread.

Fixing this should be easy:

Just retrieve the value from the onSucceeded handler, since this handler is triggered after the task's completion on the application thread anyways and using this method to retrieve the value will not block the application thread.

The following code assumes LocationModelWorker extends Task<LocationModel>

Rewrite LocationModelWorker to take Future<ConfigModel> as constuctor parameter instead of a ConfigModel parameter. Make sure get isn't called on this Future until the call method starts (e.g. don't call it from the constructor).

configModelWorkerSansUpdates.setOnSucceeded((WorkerStateEvent event) -> {
    sessionPaneController.setConfigModel(configModelWorkerSansUpdates.getValue());
});

...

Future<ConfigModel> configFuture = taskModel.submitTask(configModelWorkerSansUpdates);

LocationModelWorker locationModelWorker = new LocationModelWorker(configFuture, taskModel);
locationModelWorker.setOnSucceeded(evt -> {
    sessionPaneController.setLocationModel(locationModelWorker.getValue());
});

...

taskModel.submitTask(locationModelWorker);

You may need to adjust this a bit in case you only want to call setLocationModel and setConfigModel if both tasks complete successfully.

fabian
  • 80,457
  • 12
  • 86
  • 114
  • Thank you for responding. I have tried to retrieve the result from the `onSucceeded` handler but I am still seeing the same issue using `configModel = configFuture.get();` within the handler. Am I am doing something grossly wrong such as initialising my models in the wrong place? – swshaun Dec 04 '16 at 16:17
  • Also... I note the content of `configFuture.outcome` is null as well – swshaun Dec 04 '16 at 16:37
  • Got it! Thank you that works. I have accepted your answer – swshaun Dec 04 '16 at 16:46
  • @swshaun sorry, somewhere while editing i seem to have changed the `configModelWorkerSansUpdates.getValue()` call back to `configModel`. The `value` property of the `Task` should be up to date since it's set to the result of the call method just before changing the task's state to succeeded. – fabian Dec 04 '16 at 16:47
  • Thank you for the clarification. I am much clearer now. – swshaun Dec 04 '16 at 16:49
0

I found further information from another posting! Task is a subclass of Runnable not Callable

As stated in this post:

FX Task, returning a value always returns null

The Javadoc shows that:

The Future's get method will return null upon successful completion.

See here for the Javadoc the above excerpt is from.

I made an erroneous assumption that Task is an implementation of Callable when in fact this is not the case.

Therefore Future.get() will return null on successfull completion as a Runnable has no return result.

Community
  • 1
  • 1
swshaun
  • 384
  • 1
  • 4
  • 12