utilitybasedfx.MainGUIController.java Source code

Java tutorial

Introduction

Here is the source code for utilitybasedfx.MainGUIController.java

Source

/*
 * 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 utilitybasedfx;

import bikeshed.*;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Calendar;
import java.util.Optional;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.ChoiceDialog;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextInputDialog;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.CheckBoxTreeCell;
import javafx.scene.paint.Paint;
import org.apache.commons.io.FileUtils;

/**
 * FXML Controller class
 *
 * @author David
 */
public class MainGUIController implements Initializable {
    @FXML
    private MenuItem mnuFileNew;
    @FXML
    private MenuItem mnuFileOpen;

    @FXML
    private Label lblProjectName;
    @FXML
    private Label lblSourceStatus;
    @FXML
    private Label lblCompiledStatus;
    @FXML
    private Button btnImportSource;
    @FXML
    private Button btnImportCompiled;
    @FXML
    private Button btnCompileFromSource;
    @FXML
    private Button btnRecheckProjectFolders;
    @FXML
    private TreeView treeFilesToMutate;
    @FXML
    private TreeView treeTraditionalOps;
    @FXML
    private TreeView treeClassOps;
    @FXML
    private Button btnGenerate;
    @FXML
    private Label lblWorking;
    @FXML
    private ProgressIndicator barWorking;
    @FXML
    private Label lblWorkingTask;

    ArrayList<File> listOfSelectedFiles = new ArrayList<File>();
    ArrayList<String> listOfTraditionalOps = new ArrayList<String>();
    ArrayList<String> listOfClassOps = new ArrayList<String>();

    boolean shouldRefreshFileTree;

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

        Prefs.setProjectLoaded(false);

        setupOpsTrees();

        shouldRefreshFileTree = false;

        //checkProjectLoaded();
    }

    @FXML
    public void eventFileNew(ActionEvent event) {
        SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd_HHmmss");
        TextInputDialog dialog = new TextInputDialog("Project_" + sdf.format((Calendar.getInstance().getTime())));
        dialog.setTitle("Please specify a project name");

        dialog.setHeaderText("This will be the name of your project and cannot easily be changed.\n"
                + "The name should be a legal filename.");

        dialog.setContentText("New project name:");

        boolean shouldContinue = true;
        boolean resultValid = false;
        String res = "";

        do {
            Optional<String> result = dialog.showAndWait();
            if (result.isPresent()) {
                res = result.get();
                if (Utils.validFileName(res)) {
                    if (Utils.dirExist(Prefs.getSavePath() + "/" + res)) {
                        dialog.setHeaderText("This will be the name of your project and cannot easily be changed.\n"
                                + "The name should be a legal filename.\n\n" + "This project already exists");
                    } else {
                        resultValid = true;
                        shouldContinue = false;
                    }
                } else {
                    dialog.setHeaderText("This will be the name of your project and cannot easily be changed.\n"
                            + "The name should be a legal filename.\n\n"
                            + "The name you specified is not legal, try again");
                }
            } else {
                shouldContinue = false;
            }
        } while (shouldContinue);

        if (resultValid) {
            createProject(res);
            setActiveProject(res);
        }
    }

    @FXML
    public void eventFileOpen(ActionEvent event) {
        boolean success = false;
        boolean projectsFound = false;

        File files[] = new File(Prefs.getSavePath()).listFiles();

        if (files != null) {
            ArrayList<String> choices = new ArrayList<String>();

            for (File f : files) {
                if (f != null && f.isDirectory()) {
                    choices.add(f.getName());
                }
            }

            if (!choices.isEmpty()) {
                ChoiceDialog<String> dialog = new ChoiceDialog<String>(choices.get(0), choices);
                dialog.setTitle("Open Project");
                dialog.setHeaderText("Please choose a project you would like to load");
                dialog.setContentText("Project:");

                Optional<String> result = dialog.showAndWait();

                if (result.isPresent()) {
                    success = true;
                    setActiveProject(result.get());
                }

                projectsFound = true;
            }
        }

        if (!success) {
            shouldRefreshFileTree = true;
        }

        if (!projectsFound) {
            Alert alert = new Alert(AlertType.ERROR);
            alert.setTitle("No projects found");
            alert.setHeaderText("There are no projects found in the following folder");
            alert.setContentText(new File(Prefs.getSavePath()).getAbsolutePath());

            alert.showAndWait();
        }
    }

    @FXML
    private void eventFileClose(ActionEvent event) {
        Platform.exit();
        System.exit(0);
    }

    @FXML
    public void eventImportSource(ActionEvent event) {
        File selectedDir = Utils.dirChooser("Please choose the project source root folder");
        File sourceDir = new File(Prefs.getSourcePath());

        boolean shouldContinue = true;

        if (selectedDir != null) {
            if (sourceDir.isDirectory()) {
                Alert alert = new Alert(AlertType.CONFIRMATION);
                alert.setTitle("This project already exists!");
                alert.setHeaderText("This project already has a src folder meaning it already has files imported");
                alert.setContentText(
                        "Are you sure you want to import the new source?\nPressing OK will delete the files previous imported to this project and replace them with the new files.");

                Optional<ButtonType> result = alert.showAndWait();
                if (result.get() == ButtonType.OK) {
                    sourceDir.delete();
                } else {
                    shouldContinue = false;
                }
            }

            if (shouldContinue) {
                shouldRefreshFileTree = true;
                enableWorking(new Task<String>() {
                    @Override
                    protected String call() throws InterruptedException {
                        updateMessage("Copying Files...");
                        try {
                            FileUtils.copyDirectory(selectedDir, sourceDir);
                        } catch (IOException e) {
                            Utils.stackErrorDialog(e);
                        }
                        updateMessage("");

                        return ""; //[FIXME] find a way to do this inline function without a class as return type
                    }
                });
            }
        }
    }

    @FXML
    private void eventImportCompiled(ActionEvent event) {
        File selectedDir = Utils.dirChooser("Please choose the project classes root folder");
        File classDir = new File(Prefs.getClassPath());

        boolean shouldContinue = true;

        if (selectedDir != null) {
            if (classDir.isDirectory()) {
                Alert alert = new Alert(AlertType.CONFIRMATION);
                alert.setTitle("This project already exists!");
                alert.setHeaderText(
                        "This project already has a class folder meaning it already has files imported");
                alert.setContentText(
                        "Are you sure you want to import the new source?\nPressing OK will delete the files previous imported to this project and replace them with the new files.");

                Optional<ButtonType> result = alert.showAndWait();
                if (result.get() == ButtonType.OK) {
                    classDir.delete();
                } else {
                    shouldContinue = false;
                }
            }

            if (shouldContinue) {
                shouldRefreshFileTree = true;
                enableWorking(new Task<String>() {
                    @Override
                    protected String call() throws InterruptedException {
                        updateMessage("Copying Files...");
                        try {
                            FileUtils.copyDirectory(selectedDir, classDir);
                        } catch (IOException e) {
                            Utils.stackErrorDialog(e);
                        }
                        updateMessage("");

                        return ""; //[FIXME] find a way to do this inline function without a class as return type
                    }
                });
            }
        }
    }

    @FXML
    private void eventCompileFromSource(ActionEvent event) {
        // [TODO] use javac to compile from source
        Alert alert = new Alert(AlertType.ERROR);
        alert.setTitle("This is not yet implemented");
        alert.setHeaderText("in the future pressing this will generate the .class files from the source");
        alert.setContentText("but as of now it is not implemented");

        alert.showAndWait();

        shouldRefreshFileTree = true;
    }

    @FXML
    private void eventRecheckProjectFolders(ActionEvent event) {
        shouldRefreshFileTree = true;
        checkProjectLoaded();
    }

    @FXML
    private void eventGenerateMutants(ActionEvent event) {
        MutantViewerController c = Prefs.getController("MutantViewer");

        List<File> actualFiles = new ArrayList<>();
        for (File file : listOfSelectedFiles) {
            actualFiles.add(new File(Prefs.getSourcePath(), file.toString()));
        }

        enableWorking(new Task<String>() {
            @Override
            protected String call() throws InterruptedException {
                File root;
                try {
                    root = new File(Prefs.getProjectPath()).getCanonicalFile();
                } catch (IOException ioe) {
                    updateMessage("Unable to find root directory");
                    return "";
                }
                NewMutationSystem nms = new NewMutationSystem(root);

                updateMessage("Generating Mutants!...");
                try {
                    List<Mutant> mutants = nms.generateMutants(actualFiles, listOfTraditionalOps, listOfClassOps);
                    updateMessage("");

                    c.setMutants(mutants);
                    Platform.runLater(c::refreshClassesCbos);
                    Platform.runLater(() -> {
                        Prefs.getStage().setScene(Prefs.getScene("MutantViewer"));
                    });
                } catch (UnableToGenerateException utg) {
                    Platform.runLater(() -> {
                        Alert alert = new Alert(AlertType.ERROR);
                        alert.setTitle("There was an error");
                        alert.setHeaderText("There was an issue generating mutants");
                        alert.setContentText(utg.toString());

                        alert.showAndWait();
                    });
                } catch (NoClassFilesException ex) {
                    Platform.runLater(() -> {
                        Alert alert = new Alert(AlertType.ERROR);
                        alert.setTitle("There was an error");
                        alert.setHeaderText("There was an issue generating mutants");
                        alert.setContentText(
                                "There is no classes in the following folder\n" + Prefs.getClassPath());

                        alert.showAndWait();
                    });
                }

                return ""; //[FIXME] find a way to do this inline function without a class as return type
            }
        });
    }

    private void createProject(String res) {
        Prefs.setProjectName(res);

        File f = new File(Prefs.getProjectPath());
        try {
            f.mkdirs();
        } catch (Exception e) {
            Utils.stackErrorDialog(e);
        }
    }

    private void setActiveProject(String name) {
        Prefs.setProjectName(name);
        lblProjectName.setText(name);
        Prefs.setProjectLoaded(true);
        shouldRefreshFileTree = true;
        checkProjectLoaded();
    }

    public boolean checkProjectLoaded() {
        boolean fullyLoaded = true;
        if (Prefs.getProjectLoaded()) {
            btnImportSource.setDisable(false);
            btnImportCompiled.setDisable(false);
            btnRecheckProjectFolders.setDisable(false);
            if (Prefs.getSourceImported()) {
                lblSourceStatus.setText("Found");
                lblSourceStatus.setTextFill(Paint.valueOf("GREEN"));
                btnCompileFromSource.setDisable(false);
            } else {
                lblSourceStatus.setText("Not Found");
                lblSourceStatus.setTextFill(Paint.valueOf("RED"));
                btnCompileFromSource.setDisable(true);
                fullyLoaded = false;
            }

            if (Prefs.getClassImported()) {
                lblCompiledStatus.setText("Found");
                lblCompiledStatus.setTextFill(Paint.valueOf("GREEN"));
            } else {
                lblCompiledStatus.setText("Not Found");
                lblCompiledStatus.setTextFill(Paint.valueOf("RED"));
                fullyLoaded = false;
            }
        } else {
            btnImportSource.setDisable(true);
            btnImportCompiled.setDisable(true);
            btnCompileFromSource.setDisable(true);
            btnRecheckProjectFolders.setDisable(true);
            fullyLoaded = false;
        }

        if (fullyLoaded) {
            if (shouldRefreshFileTree) {
                refreshFilesTree();
            }
        } else {
            clearFilesTree();
        }

        checkIfGenerateShouldBeEnabled();

        return fullyLoaded;
    }

    private void refreshFilesTree() {
        shouldRefreshFileTree = false;
        CheckBoxTreeItem<String> root;

        root = new CheckBoxTreeItem("root");
        root.setExpanded(true);

        addChildren(root, new File(Prefs.getSourcePath()));

        treeFilesToMutate.setStyle("-fx-font-size: 11; ");

        //treeFilesToMutate.getStylesheets().add("/css/treeViewColouredSelection.css"); // [FIXME] make the color change! WHY IT NO WORK

        treeFilesToMutate.setRoot(root);
        treeFilesToMutate.setShowRoot(false);
        treeFilesToMutate.setCellFactory(CheckBoxTreeCell.<String>forTreeView());
    }

    // Returns false if folder and branches have no java files
    private boolean addChildren(TreeItem parentBranch, File parentFile) {
        boolean containsJavaFiles = false;
        File children[] = parentFile.listFiles();

        if (children != null) {
            ArrayList<File> directories = new ArrayList<File>();
            ArrayList<File> files = new ArrayList<File>();

            for (File child : children) {
                if (child.isDirectory()) {
                    directories.add(child);
                } else {
                    files.add(child);
                }
            }

            for (File dir : directories) {
                CheckBoxTreeItem<String> treeItem = new CheckBoxTreeItem<>(dir.getName());
                treeItem.setExpanded(true);

                if (addChildren(treeItem, dir)) {
                    containsJavaFiles = true;
                    parentBranch.getChildren().add(treeItem);
                }
            }

            for (File file : files) {
                if (file.getName().endsWith(".java")) {
                    CheckBoxTreeItem<String> treeItem = new CheckBoxTreeItem<>(file.getName());
                    treeItem.selectedProperty().addListener(new ChangeListener<Boolean>() {
                        public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue,
                                Boolean newValue) {
                            if (newValue) {
                                addFilepathToArraylist(treeItem);
                            } else {
                                removeFilepathFromArraylist(treeItem);
                            }
                        }
                    });
                    parentBranch.getChildren().add(treeItem);
                    containsJavaFiles = true;
                }
            }
        }
        return containsJavaFiles;
    }

    private void clearFilesTree() {
        shouldRefreshFileTree = false;
        CheckBoxTreeItem<String> root;

        root = new CheckBoxTreeItem("root");
        root.setExpanded(true);

        treeFilesToMutate.setRoot(root);
        treeFilesToMutate.setShowRoot(false);
    }

    private void addFilepathToArraylist(CheckBoxTreeItem<String> treeItem) {
        String path = treeItem.valueProperty().get();
        CheckBoxTreeItem<String> parent = (CheckBoxTreeItem) treeItem.getParent();

        while (parent != null && !parent.valueProperty().get().equals("root")) {
            treeItem = parent;
            parent = (CheckBoxTreeItem) treeItem.getParent();
            path = treeItem.valueProperty().get() + "/" + path;
        }

        listOfSelectedFiles.add(new File(path));

        checkIfGenerateShouldBeEnabled();
    }

    private void removeFilepathFromArraylist(CheckBoxTreeItem<String> treeItem) {
        String path = treeItem.valueProperty().get();
        CheckBoxTreeItem<String> parent = (CheckBoxTreeItem) treeItem.getParent();

        while (parent != null && !parent.valueProperty().get().equals("root")) {
            treeItem = parent;
            parent = (CheckBoxTreeItem) treeItem.getParent();
            path = treeItem.valueProperty().get() + "/" + path;
        }

        listOfSelectedFiles.remove(new File(path));

        checkIfGenerateShouldBeEnabled();
    }

    private void setupOpsTrees() {
        CheckBoxTreeItem<String> traditionalRoot = preSetupOpsTree(treeTraditionalOps);
        CheckBoxTreeItem<String> classRoot = preSetupOpsTree(treeClassOps);

        String traditionalOps[] = { "-Arithmetic", "AOR", "AOI", "AOD", "-Relational", "ROR", "-Conditional", "COR",
                "COI", "COD", "-Shift", "SOR", "-Logical", "LOR", "LOI", "LOD", "-Assignment", "ASR", "-Deletion",
                "SDL", "VDL", "CDL", "ODL" };

        String classOps[] = { "-Encapsulation", "AMC", "-Inheritance", "IHD", "IHI", "IOD", "IOP", "IOR", "ISI",
                "ISD", "IPC", "-Polymorphism", "PNC", "PMD", "PPD", "PCI", "PCC", "PCD", "PRV", "OMR", "OMD", "OAC",
                "-Java-Specific Features", "JTI", "JTD", "JSI", "JSD", "JID", "JDC", "EOA", "EOC", "EAM", "EMM" };

        fillTreeFromArray(traditionalOps, traditionalRoot, listOfTraditionalOps);
        fillTreeFromArray(classOps, classRoot, listOfClassOps);
    }

    private void fillTreeFromArray(String[] ops, CheckBoxTreeItem<String> root, ArrayList<String> listForOpsEvent) {
        CheckBoxTreeItem<String> subFolder = null;
        for (String op : ops) {
            if (op.startsWith("-")) {
                subFolder = new CheckBoxTreeItem<String>(op.substring(1));
                subFolder.setExpanded(true);
                root.getChildren().add(subFolder);
            } else {
                CheckBoxTreeItem<String> opTreeItem = new CheckBoxTreeItem<String>(op);
                if (subFolder != null) {
                    subFolder.getChildren().add(opTreeItem);
                    opTreeItem.selectedProperty().addListener(new ChangeListener<Boolean>() {
                        public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue,
                                Boolean newValue) {
                            if (newValue) {
                                addOpToArraylist(opTreeItem, listForOpsEvent);
                            } else {
                                removeOpFromArraylist(opTreeItem, listForOpsEvent);
                            }
                        }
                    });
                }
            }
        }
    }

    private CheckBoxTreeItem<String> preSetupOpsTree(TreeView tree) {
        CheckBoxTreeItem<String> root = new CheckBoxTreeItem("root");

        root.setExpanded(true);

        tree.setStyle("-fx-font-size: 11; ");

        //tree.getStylesheets().add("/css/treeViewColouredSelection.css"); // [FIXME] make the color change! WHY IT NO WORK

        tree.setRoot(root);
        tree.setShowRoot(false);
        tree.setCellFactory(CheckBoxTreeCell.<String>forTreeView());

        return root;
    }

    private void addOpToArraylist(CheckBoxTreeItem<String> treeItem, ArrayList<String> list) {
        String opName = treeItem.valueProperty().get();

        list.add(opName);

        checkIfGenerateShouldBeEnabled();
    }

    private void removeOpFromArraylist(CheckBoxTreeItem<String> treeItem, ArrayList<String> list) {
        String opName = treeItem.valueProperty().get();

        list.remove(opName);

        checkIfGenerateShouldBeEnabled();
    }

    private void checkIfGenerateShouldBeEnabled() {
        if (listOfSelectedFiles.isEmpty() || (listOfTraditionalOps.isEmpty() && listOfClassOps.isEmpty())) {
            btnGenerate.setDisable(true);
        } else {
            btnGenerate.setDisable(false);
        }
    }

    private void enableWorking(Task task) {
        btnImportSource.setDisable(true);
        btnImportCompiled.setDisable(true);
        btnCompileFromSource.setDisable(true);
        btnGenerate.setDisable(true);

        lblWorking.setVisible(true);
        barWorking.setVisible(true);
        lblWorkingTask.setVisible(true);

        lblWorkingTask.textProperty().bind(task.messageProperty());
        barWorking.progressProperty().bind(task.progressProperty());

        task.stateProperty().addListener(new ChangeListener<Worker.State>() {
            @Override
            public void changed(ObservableValue<? extends Worker.State> observableValue, Worker.State oldState,
                    Worker.State newState) {
                if (newState == Worker.State.SUCCEEDED) {
                    disableWorking();
                } else if (newState == Worker.State.FAILED) {
                    disableWorking();

                    //Utils.stackErrorDialog((Exception)task.getException());
                    task.getException().printStackTrace();

                    Alert alert = new Alert(AlertType.ERROR);
                    alert.setTitle("There was an error");
                    alert.setHeaderText("There was some kind of error while trying to do the current task");
                    alert.setContentText(task.getException().toString());

                    alert.showAndWait();
                }
            }
        });

        new Thread(task).start();
    }

    private void disableWorking() {
        lblWorking.setVisible(false);
        barWorking.setVisible(false);
        lblWorkingTask.setVisible(false);

        checkProjectLoaded();
    }
}