John Doe
John Doe

Reputation: 423

JavaFX application is very slow

We built an information retrieval engine as part of our course project. We were asked to run our program with JavaFX.

The problem is that the project is very busy and includes: parse documents (460,000 docs and up to 3 million words), add terms to a dictionary and more functions that take time.

The time needed for the project to run without GUI is about 19 minutes; it includes merging post files and load the dictionary from disk to RAM. The issue is that when we add the GUI, the time is multiplied to reach almost 56 minutes.

We think that we did something wrong in the GUI built:

Controller

package sample;

import javafx.event.ActionEvent;
import javafx.scene.control.Alert;
import javafx.scene.control.TableView;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
import main.Indexer;
import main.ReadFile;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

public class Controller {
    public javafx.scene.control.CheckBox checkBox_Stemming;
    public javafx.scene.control.Button btn_start;
    public javafx.scene.control.Button btn_browse_Corpus;
    public javafx.scene.control.Button btn_browse_postfile;
    public javafx.scene.control.Button btn_reset;
    public javafx.scene.control.TextField tf_postfilePath;
    public javafx.scene.control.TextField tf_corpusPath;
    public javafx.scene.control.Button btn_dictionary;
    public TableView table_dic;
    public javafx.scene.control.TableColumn tc_term;
    public javafx.scene.control.TableColumn  tc_tf;
    private Stage mainStage;
    private Indexer indexer;
    private ReadFile rf;

    public void initialize(Stage mainStage) {
        this.mainStage = mainStage;
        mainStage.setMinHeight(600);
        mainStage.setMinWidth(800);
    }

    public String openFile(ActionEvent event) {
        DirectoryChooser chooser = new DirectoryChooser();
        File defaultDirectory = new File("C:\\");
        chooser.setInitialDirectory(defaultDirectory);
        File selectedDirectory = chooser.showDialog(new Stage());
        return selectedDirectory.getPath();
    }

    public void setStopWord(ActionEvent event){
        tf_postfilePath.textProperty().setValue(openFile(event));
    }

    public void setCorpusPath(ActionEvent event){
        tf_corpusPath.textProperty().setValue(openFile(event));
    }

    public void startIndex(ActionEvent event) {
        String corpusPath = tf_corpusPath.textProperty().getValue();
        String postfilePath = tf_postfilePath.textProperty().getValue();

        if(corpusPath.length() > 0 && postfilePath.length() > 0 ){
            long startTime = System.currentTimeMillis();
            indexer=new Indexer();
            rf= new ReadFile();

            try { 


   indexer.Start(rf,corpusPath,checkBox_Stemming.isSelected(),postfilePath);
            } catch (IOException e) {
                e.printStackTrace();
            }

            long stopTime = System.currentTimeMillis();
            long elapsedTime = stopTime - startTime;
            long minutes = TimeUnit.MILLISECONDS.toMinutes(elapsedTime);
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setTitle("Information");
            alert.setHeaderText("Look, an Information Dialog");
            alert.setContentText("Number of documents:"+ main.Indexer.n+"\n"
                    +"Number of uniq terms:"+ "\n"+"Running time:"+minutes+ "minutes\n");

            alert.showAndWait();
        } else if (postfilePath.length() > 0) {
            indexer = new Indexer();
            try {
                indexer.createFinalDictionary(postfilePath);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            Alert alert = new Alert(Alert.AlertType.ERROR);
            alert.setTitle("Problem");
            alert.setHeaderText("Look, an Information Dialog");
            alert.setContentText("The path are empty or both not. please insert only one path postingfile or corpus");

            alert.showAndWait();
        }
    }

    public void resetIndexer(ActionEvent event) {
        Alert alert = new Alert(Alert.AlertType.INFORMATION);
        alert.setTitle("Information");
        alert.setHeaderText("Look, an Information Dialog");
        alert.setContentText("The dictionary and the postingfile deleted");
        alert.showAndWait();
        indexer = null;
        rf = null;
        tf_postfilePath.textProperty().setValue("");
        tf_corpusPath.textProperty().setValue("");
    }

    public void showDictionary(ActionEvent event) {
        System.out.println("hello");

        /**if(indexer != null) {
            HashMap<String, String> dic = indexer.getDic();
            List<String> sortedKeys = new ArrayList(dic.keySet());
            Collections.sort(sortedKeys);
            for (String k:sortedKeys) {
            table_dic.getColumns().addAll(k);
        } else {
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setTitle("Information");
            alert.setHeaderText("Look, an Information Dialog");
            alert.setContentText("The dictionary is empty");
            alert.showAndWait();
        }*/
    }
}

FXML file

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Text?>

<AnchorPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="800.0" prefWidth="1200.0" xmlns="http://javafx.com/javafx/11.0.1">
    <Button fx:id="btn_reset" layoutX="33.0" layoutY="70.0" mnemonicParsing="false" text="Reset" onAction='#resetIndexer' />
    <Button fx:id="btn_browse_Corpus" layoutX="129.0" layoutY="112.0" mnemonicParsing="false" onAction="#setCorpusPath" text="Browse" />
    <Button fx:id="btn_browse_postfile" layoutX="129.0" layoutY="149.0" mnemonicParsing="false" onAction="#setStopWord" text="Browse" />

    <TableView fx:id="table_dic" layoutX="451.0" layoutY="70.0" prefHeight="439.0" prefWidth="427.0">
        <columns>
            <TableColumn fx:id="tc_term" prefWidth="211.20001220703125" text="Term " />
            <TableColumn fx:id="tc_tf" minWidth="2.4000244140625" prefWidth="215.20001220703125" text="tf" />
        </columns>
    </TableView>

    <TextField fx:id="tf_postfilePath" layoutX="204.0" layoutY="148.0" prefHeight="26.0" prefWidth="218.0" />
    <TextField fx:id="tf_corpusPath" layoutX="205.0" layoutY="111.0" prefHeight="26.0" prefWidth="217.0" />
    <CheckBox fx:id="checkBox_Stemming" layoutX="125.0" layoutY="74.0" mnemonicParsing="false" text="Stemming" />
    <Button fx:id="btn_dictionary" layoutX="39.0" layoutY="233.0" mnemonicParsing="false" text="Dictionary" onAction="#showDictionary" />
    <Button fx:id="btn_start" layoutX="35.0" layoutY="187.0" mnemonicParsing="false" text="Start" onAction='#startIndex' />
    <Text layoutX="46.0" layoutY="129.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Corpus" />
    <Text layoutX="33.0" layoutY="163.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Posting files" wrappingWidth="75.0" />
</AnchorPane>

Main class

package sample;

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

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("IR2020");
        primaryStage.setScene(new Scene(root, 300, 275));
        Controller controller=new Controller();
        controller.initialize(primaryStage);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

I would appreciate your help, thank you.

Upvotes: 3

Views: 2539

Answers (1)

0009laH
0009laH

Reputation: 1990

I follow @Slaw and @LuxusProblem answers ; you should create a service whose only role is to execute the instruction indexer.Start(rf,corpusPath,checkBox_Stemming.isSelected(),postfilePath);. Then in your controller, you have to start this service then listen to the state of this service ; if it switches to SUCCEEDED, then you can display your message box.

I've implemented a basic service for your code (maybe there will be little mistakes, as I don't know how Indexer and ReadFile classes are made):

package sample;

import java.util.concurrent.TimeUnit;

import javafx.concurrent.Service;
import javafx.concurrent.Task;

public class IndexService extends Service<Boolean> {
    private String corpusPath;
    private String postFilePath;
    private ReadFile readFile;
    private boolean stemming;

    public IndexService(String cPath, String pfPath, ReadFile rf, boolean stem) {
        corpusPath = cPath;
        postFilePath = pfPath;
        readFile = rf;
        stemming = stem;
    }

    @Override
    protected Task<Boolean> createTask() {
        return new Task<Boolean>() {
            @Override
            protected Boolean call() throws Exception {
                try { 
                    indexer.Start(readFile, corpusPath, stem, postfilePath);
                    return true;
                } catch (IOException e) {
                    e.printStackTrace();
                    return false;
                }
            }
        };
    }
}

In your controller, the startIndex method will look like this:

@FXML
public void startIndex(ActionEvent event) throws InterruptedException {
    String corpusPath = tf_corpusPath.textProperty().getValue();
    String postfilePath = tf_postfilePath.textProperty().getValue();

    if (corpusPath.length() > 0 && postfilePath.length() > 0) {
        long startTime = System.currentTimeMillis();
        IndexService service = new IndexService(corpusPath, postfilePath, rf, checkBox_Stemming.isSelected());
        service.start();

        service.stateProperty().addListener((bean_p, old_p, new_p) -> {
            switch (new_p) {
                case SUCCEEDED:
                    long stopTime = System.currentTimeMillis();
                    long elapsedTime = stopTime - startTime;
                    long minutes = TimeUnit.MILLISECONDS.toMinutes(elapsedTime);

                    Alert alert = new Alert(Alert.AlertType.INFORMATION);
                    alert.setTitle("Information");
                    alert.setHeaderText("Look, an Information Dialog");
                    alert.setContentText("Number of documents:" + "main.Indexer.n" + "\n" + "Number of uniq terms:" + "\n" + "Running time:" + minutes + "minutes\n");

                    alert.showAndWait();
                    break;
                default:
                    break;
            }
        });
    } else if (postfilePath.length() > 0) {
        indexer = new Indexer();
        try {
            indexer.createFinalDictionary(postfilePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    } else {
        Alert alert = new Alert(Alert.AlertType.ERROR);
        alert.setTitle("Problem");
        alert.setHeaderText("Look, an Information Dialog");
        alert.setContentText("The path are empty or both not. please insert only one path postingfile or corpus");

        alert.showAndWait();
    }
}

UPDATE

If you want to go further, you can even manage exceptions that are thrown into your service. When an error is thrown, your IndexService switches to FAILED so you can display it.

On the IndexService class, just remove the try-catch :

    @Override
    protected Task<Boolean> createTask() {
        return new Task<Boolean>() {
            @Override
            protected Boolean call() throws Exception {
                indexer.Start(readFile, corpusPath, stem, postfilePath);
                return true;
            }
        };
    }

In your controller, add a case for State.FAILED into your service state listener:

case FAILED:
    Alert alert = new Alert(Alert.AlertType.ERROR);
    alert.setTitle("Error");
    alert.setHeaderText("Look, an Information Dialog");
    alert.setContentText(service.getException().getMessage());

    alert.showAndWait();
    break;

I hope it will help you :)

Upvotes: 3

Related Questions