Reputation: 423
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
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