0

I am making this JavaFX mass calculator, that essentially consists of a button that executes a SwingWorker background thread to calculate certain ratios or factors or whatnot.

I've realized that for my factor and prime factor calculations (when I try to find all the factors and prime factors of a number the user inputs), I get this NullPointerException after the calculation has been performed about 3-5 times.

Here's the script that executes when the button is clicked:

public static longCalculationThread calculationThread;

//Called by the calculate button
@FXML
void callCalculation(ActionEvent event) throws IOException, InterruptedException {

    Long number = 0L;
    Long lowerBound = 0L;
    Long higherBound = 0L;

    try {
        //Bunch of input verification logic here. The logic here also creates the variables for the thread bundle

        //Enabling progress UI since no errors were encoundered...
        excelPane.setVisible(false); 
        progressPane.setDisable(false);
        progressPane.setVisible(true);

        //Preparing threadBundle, which is just a bunch of fields out into a data holding class [not shown]

        calculationThread = new longCalculationThread(threadBundle);
        calculationThread.execute();
        System.gc(); //For the objects of the previous calculationThread
    }
}  

And here's how the longCalculationThread is structured:

package PrimusForPC;

public class longCalculationThread extends SwingWorker<Void, Void> {

private ArrayList<Long> longResults = new ArrayList<>();
private ArrayList<String> stringResults = new ArrayList<>();

private calculationHolder.calculationType type;
private JFXProgressBar progressBar;
private Text progressText;
private GridView<String> answerGridView;
private Pane mainPane, progressPane;
private long mainNumber, lowerNum, upperNum;
private boolean shouldExport;
private ExcelWriter.spreadsheetTypes exportType;
private int excelExportLimit;
private JFXButton calcButton;
private Menu selectionMenu;

private static double acceptableAccuracy = 0.004; //The degree of accuracty for pi and phi couples
public static final double PHI = 1.6180339887498948482045;

public boolean shouldDie; //Called externally to kill the thread. Hasn't been implemented yet


longCalculationThread(threadArgumentBundle bundle){
    progressBar = bundle.progressBar;
    answerGridView = bundle.answerGridView;
    type = bundle.type;
    mainNumber = bundle.primaryNumber;
    upperNum = bundle.upperNum;
    lowerNum = bundle.lowerNum;
    progressText = bundle.progressText;
    mainPane = bundle.mainPane;
    progressPane = bundle.progressPane;
    exportType = bundle.exportType;
    shouldExport = bundle.shouldExport;
    excelExportLimit = bundle.excelExportLimit;   
    calcButton = bundle.button;
    selectionMenu = bundle.selectionMenu;   
}


@Override
public Void doInBackground(){
    System.out.println("Thread started " + Thread.currentThread().getName());
    Platform.runLater(new Runnable() {
    @Override
    public void run() {
        progressBar.isIndeterminate();
        progressBar.progressProperty().setValue(0);
        calcButton.setDisable(true);
        selectionMenu.setDisable(true);
      }
    });

    switch (type){
        case factors:
            findfactors();
            break;
        case primeFactors:
            findPrimeFactors();
            break;
        case primesUpToX:
            findPrimesUpToX();
            break;
//Bunch of other cases here...       
    }
    try {
        publishResults();
    } catch (IOException | InterruptedException ex) {
        Logger.getLogger(longCalculationThread.class.getName()).log(Level.SEVERE, null, ex);
    }
    reEndableInterface();
    System.gc();
    System.out.println("Thread ended " + Thread.currentThread().getName() + "\n");
    return null;
}


static private long getSquareRoot (long number){
    //For calculations square root only matters for larger numbers
    //for numbers below 5 it doesn't really matter
    if (number > 5){
        number =  (long) Math.ceil(Math.sqrt(number));
    }
return number;
}


private void findfactors(){
    progressText.setText("Finding factors...");
    long root = getSquareRoot(mainNumber);
    double incrementValue = 1/(double)root;
      for (long divisor = 1; divisor <= root; divisor++) {
       setProgressBar(progressBar.getProgress() + incrementValue);
       if (mainNumber % divisor == 0) {
           longResults.add(divisor);
           longResults.add((mainNumber/divisor));
       }
   }
}


private void findPrimeFactors(){
    findfactors();
    progressText.setText("Separating prime factors from composite factors...");
    progressBar.progressProperty().set(0);
    double incrementValue = 1/(double)longResults.size();
    ArrayList <Long> realResults = new ArrayList<>();
    for (long factor: longResults){
       if (isPrime(factor)) realResults.add(factor);
       setProgressBar(progressBar.getProgress() + incrementValue);
    }

    longResults = realResults;
}


static boolean isPrime(long number){
    if (number == 1) return false;
    if (number == 2 || number == 3 || number == 5) return true;
    long root = getSquareRoot(number);
    for (int divisor = 2; divisor <= root; divisor++)
        if (number % divisor == 0) return false;
    return true;
}


private void publishResults() throws IOException, InterruptedException{

        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                answerGridView.getItems().setAll(stringResults);
              }
            });          

//To make UI interface useable again
private void reEndableInterface(){
     Platform.runLater(new Runnable() {
            @Override
            public void run() {
                progressPane.setDisable(true);
                progressPane.setVisible(false);
                mainPane.setDisable(false);
                mainPane.setVisible(true);
                calcButton.setDisable(false);
                System.out.println("Button enabled " + Thread.currentThread().getName());
                selectionMenu.setDisable(false);
              }
            });
}

I'm not exactly sure where this NullPointerException is coming from, or why it seems to only surface when I'm using the "find factors" or "find prime factors" calculations. Here is a look at the error log (sorry it's so long, didn't know where to cut off).

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at com.sun.javafx.text.PrismTextLayout.addTextRun(PrismTextLayout.java:755)
at com.sun.javafx.text.GlyphLayout.addTextRun(GlyphLayout.java:140)
at com.sun.javafx.text.GlyphLayout.breakRuns(GlyphLayout.java:312)
at com.sun.javafx.text.PrismTextLayout.buildRuns(PrismTextLayout.java:770)
at com.sun.javafx.text.PrismTextLayout.layout(PrismTextLayout.java:1021)
at com.sun.javafx.text.PrismTextLayout.ensureLayout(PrismTextLayout.java:223)
at com.sun.javafx.text.PrismTextLayout.getBounds(PrismTextLayout.java:246)
at javafx.scene.text.Text.getLogicalBounds(Text.java:358)
at javafx.scene.text.Text.impl_computeGeomBounds(Text.java:1168)
at javafx.scene.Node.updateGeomBounds(Node.java:3577)
at javafx.scene.Node.getGeomBounds(Node.java:3530)
at javafx.scene.Node.getLocalBounds(Node.java:3478)
at javafx.scene.Node.updateTxBounds(Node.java:3641)
at javafx.scene.Node.getTransformedBounds(Node.java:3424)
at javafx.scene.Parent.getChildTransformedBounds(Parent.java:1724)
at javafx.scene.Parent.updateCachedBounds(Parent.java:1588)
at javafx.scene.Parent.recomputeBounds(Parent.java:1527)
at javafx.scene.Parent.impl_computeGeomBounds(Parent.java:1380)
at javafx.scene.layout.Region.impl_computeGeomBounds(Region.java:3078)
at javafx.scene.Node.updateGeomBounds(Node.java:3577)
at javafx.scene.Node.getGeomBounds(Node.java:3530)
at javafx.scene.Node.getLocalBounds(Node.java:3478)
at javafx.scene.Node.updateTxBounds(Node.java:3641)
at javafx.scene.Node.getTransformedBounds(Node.java:3424)
at javafx.scene.Parent.getChildTransformedBounds(Parent.java:1724)
at javafx.scene.Parent.updateCachedBounds(Parent.java:1588)
at javafx.scene.Parent.recomputeBounds(Parent.java:1527)
at javafx.scene.Parent.impl_computeGeomBounds(Parent.java:1380)
at javafx.scene.layout.Region.impl_computeGeomBounds(Region.java:3078)
at javafx.scene.Node.updateGeomBounds(Node.java:3577)
at javafx.scene.Node.getGeomBounds(Node.java:3530)
at javafx.scene.Node.getLocalBounds(Node.java:3478)
at javafx.scene.Node.updateTxBounds(Node.java:3641)
at javafx.scene.Node.getTransformedBounds(Node.java:3424)
at javafx.scene.Parent.getChildTransformedBounds(Parent.java:1724)
at javafx.scene.Parent.updateCachedBounds(Parent.java:1588)
at javafx.scene.Parent.recomputeBounds(Parent.java:1527)
at javafx.scene.Parent.impl_computeGeomBounds(Parent.java:1380)
at javafx.scene.layout.Region.impl_computeGeomBounds(Region.java:3078)
at javafx.scene.Node.updateGeomBounds(Node.java:3577)
at javafx.scene.Node.getGeomBounds(Node.java:3530)
at javafx.scene.Node.getLocalBounds(Node.java:3478)
at javafx.scene.Node.impl_intersectsBounds(Node.java:5013)
at javafx.scene.layout.Region.impl_pickNodeLocal(Region.java:2931)
at javafx.scene.Node.impl_pickNode(Node.java:4912)
at javafx.scene.layout.Region.impl_pickNodeLocal(Region.java:2936)
at javafx.scene.Node.impl_pickNode(Node.java:4912)
at javafx.scene.Scene$MouseHandler.pickNode(Scene.java:3899)
at javafx.scene.Scene$MouseHandler.access$1600(Scene.java:3485)
at javafx.scene.Scene.pick(Scene.java:1942)
at javafx.scene.Scene.access$6700(Scene.java:159)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3799)
at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:352)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:275)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$355(GlassViewEventHandler.java:388)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:387)
at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
at com.sun.glass.ui.View.notifyMouse(View.java:937)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$149(WinApplication.java:191)
at java.lang.Thread.run(Thread.java:745)

And then at the end the console just spams:

    java.lang.ArrayIndexOutOfBoundsException

indefinitely.

As I said, I really don't know why this is happening. The calculation works for the first few times, then this error occurs and freezes the application, requiring a complete restart.

Gerold Broser
  • 14,080
  • 5
  • 48
  • 107
Andy
  • 357
  • 2
  • 17

1 Answers1

0

Thanks to the people who commented my question - the links revealed the problem!

I was using a SwingWorker because I wanted to be able to influence the UI thread directly (since Program.runLater can flood the UI thread if called repeatedly and frequently).

It turns out SwingWorkers can't have that functionality in JavaFX, so me trying to affect the UI thread directly was causing those errors. So, I went back to using a Thread, and used the throttling logic that was provided by the link in the comments. That allowed me to use Program.runLater in a Thread without flooding the UI thread.

Andy
  • 357
  • 2
  • 17