ca.wumbo.doommanager.client.controller.file.DXMLProjectCreatorController.java Source code

Java tutorial

Introduction

Here is the source code for ca.wumbo.doommanager.client.controller.file.DXMLProjectCreatorController.java

Source

/*
 * DoomManager
 * Copyright (C) 2014  Chris K
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package ca.wumbo.doommanager.client.controller.file;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Date;
import java.util.regex.Pattern;

import javax.annotation.PostConstruct;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import ca.wumbo.doommanager.client.util.Controllable;
import ca.wumbo.doommanager.client.util.Resources;
import ca.wumbo.doommanager.client.util.SelfInjectableController;
import ca.wumbo.doommanager.file.dxml.DXML;
import ca.wumbo.doommanager.file.dxml.DXMLCompilation;
import ca.wumbo.doommanager.file.dxml.DXMLLayout;
import ca.wumbo.doommanager.file.dxml.DXMLProjectInfo;
import ca.wumbo.doommanager.file.dxml.DXMLSource;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;

/**
 * This is a new wizard which will help the user set up a new DXML project.
 */
public class DXMLProjectCreatorController extends SelfInjectableController implements Controllable {

    /**
     * The logger for this class.
     */
    private static final Logger log = LogManager.getLogger(DXMLProjectCreatorController.class);

    /**
     * The regex that will check for if it's a folder type or not. It should be
     * in a form that looks like any of the following: <br>
     * <br>
     * Good:
     * <ul>
     * <li>src/</li>
     * <li>myfolder/folder/_folder.yes/</li>
     * </ul>
     * Bad:
     * <ul>
     * <li>/src/</li>
     * <li>$invalidcharacter$(#@!</li>
     * <li>/</li>
     * <li>no\windows\TBA\soon</li>
     * </ul><br>
     */
    private static final Pattern subFolderPattern = Pattern.compile("([0-9A-Za-z_.]+\\/)*[0-9A-Za-z_.]+\\/");

    /**
     * The finalized data from the user input.
     */
    private DXML dxmlData;

    /**
     * If the user aborted. By default this is true, and only set to false when
     * the DXML file is finalized and generated.
     */
    private boolean wasClosed;

    /**
     * The stage window that will contain the scene with this object.
     */
    private Stage parentStage;

    /**
     * The stage of this controller.
     */
    private Stage stage;

    /**
     * A tracker for which pane the wizard is on.
     */
    private DXMLProjectPaneState paneState;

    @Autowired
    private Resources resources;

    @Value("${dxmlprojectcreator.controller.fxmlpath}")
    private String fxmlPath;

    //=========================================================================

    @FXML
    private BorderPane rootBorderPane;

    @FXML
    private StackPane topStackPane;

    @FXML
    private Rectangle shadedRectangle;

    @FXML
    private ImageView folderImageIcon;

    @FXML
    private StackPane middleStackPane;

    @FXML
    private Button nextButton;

    @FXML
    private Button backButton;

    @FXML
    private Button cancelButton;

    //-------------------------------------------------------------------------

    @FXML
    private Pane backgroundPane;

    @FXML
    private Rectangle backgroundShape;

    //-------------------------------------------------------------------------

    @FXML
    private BorderPane projectInfoBorderPane;

    @FXML
    private Pane projectInfoPane;

    @FXML
    private TextField projectFolderTextfield;

    @FXML
    private TextField projectNameTextfield;

    @FXML
    private TextField projectAuthorsTextfield;

    @FXML
    private TextField projectWebsiteTextfield;

    @FXML
    private Button browseButton;

    //-------------------------------------------------------------------------

    @FXML
    private Pane compilationPane;

    @FXML
    private TextField compilationFileNameTextField;

    @FXML
    private TextField compilationVersionTextField;

    @FXML
    private ComboBox<String> compilationPackagingComboBox;

    //-------------------------------------------------------------------------

    @FXML
    private Pane layoutPane;

    @FXML
    private TextField layoutSourceTextField;

    @FXML
    private TextField layoutResourcesTextField;

    @FXML
    private TextField layoutBuildTextField;

    //-------------------------------------------------------------------------

    @FXML
    private Pane sourcePane;

    @FXML
    private ComboBox<String> licenseComboBox;

    @FXML
    private ComboBox<String> sourceControlComboBox;

    @FXML
    private TextField sourceURLTextField;

    //=========================================================================

    /**
     * Only to be instantiated by Spring.
     */
    private DXMLProjectCreatorController() {
        dxmlData = new DXML();
        wasClosed = true; // Assume by default until the final file is made.
        paneState = DXMLProjectPaneState.PROJECT_INFO; // Always start on the first page.
    }

    @FXML
    private void initialize() {
        // This image has no icon, so let's set it with a nice looking one.
        folderImageIcon.setImage(resources.getImage("dxmlfolderlarge"));

        // If anything changes in the fields, notify the updater.
        projectFolderTextfield.setOnKeyReleased((event) -> projectInfoTextUpdated());
        projectNameTextfield.setOnKeyReleased((event) -> projectInfoTextUpdated());
        projectAuthorsTextfield.setOnKeyReleased((event) -> projectInfoTextUpdated());
        projectWebsiteTextfield.setOnKeyReleased((event) -> projectInfoTextUpdated());
        compilationFileNameTextField.setOnKeyReleased((event) -> compilationTextUpdated());
        compilationVersionTextField.setOnKeyReleased((event) -> compilationTextUpdated());
        layoutSourceTextField.setOnKeyReleased((event) -> layoutTextUpdated());
        layoutResourcesTextField.setOnKeyReleased((event) -> layoutTextUpdated());
        layoutBuildTextField.setOnKeyReleased((event) -> layoutTextUpdated());

        // We want the base values to be selected.
        compilationPackagingComboBox.getSelectionModel().select(0);
        licenseComboBox.getSelectionModel().select(0);
        sourceControlComboBox.getSelectionModel().select(0);

        // Remove the panes from the middle stack pane so we can start the wizard on the first pane.
        // Then add in the children, we want the background pane and the first pane.
        middleStackPane.getChildren().clear();
        middleStackPane.getChildren().add(projectInfoPane);

        // This will make it so we don't always focus on the first textfield at the top.
        Platform.runLater(() -> rootBorderPane.requestFocus());
    }

    /**
     * Loads the FXML data and injects it into this object. Should be called by
     * Spring right after the constructor and dependencies are linked. To 
     * reduce code duplication, this functionality was moved to a containing
     * class.
     * 
     * @throws NullPointerException
     *       If the FXML path is null.
     * 
     * @throws RuntimeException
     *       If the FXML file is missing or corrupt.
     */
    @PostConstruct
    public void loadFXML() {
        super.loadFXML(fxmlPath);
    }

    /**
     * Tells the wizard to exit.
     */
    public void exitWizard() {
        if (stage == null) {
            log.error("Attempting to close a stage without it being set.");
            return;
        }
        stage.fireEvent(new WindowEvent(parentStage, WindowEvent.WINDOW_CLOSE_REQUEST));
    }

    /**
     * Generates the DXML file upon completion of the wizard.
     */
    private void generateDXML() {
        DXMLProjectInfo projectInfo = dxmlData.getProjectInfo();
        projectInfo.setProjectLocationPath(projectFolderTextfield.getText());
        projectInfo.setName(projectNameTextfield.getText());
        projectInfo.setAuthors(projectAuthorsTextfield.getText());
        projectInfo.setDate(new Date()); // Set the date as today.
        projectInfo.setUrl(projectWebsiteTextfield.getText());

        DXMLCompilation compilation = dxmlData.getCompilation();
        compilation.setFilename(compilationFileNameTextField.getText());
        compilation.setVersion(compilationVersionTextField.getText());
        compilation.setPackaging(compilationPackagingComboBox.getSelectionModel().getSelectedItem());

        DXMLLayout layout = dxmlData.getLayout();
        layout.setSource(layoutSourceTextField.getText());
        layout.setResources(layoutResourcesTextField.getText());
        layout.setBuild(layoutBuildTextField.getText());

        DXMLSource source = dxmlData.getSource();
        source.setLicense(licenseComboBox.getSelectionModel().getSelectedItem());
        source.setRepositoryType(sourceControlComboBox.getSelectionModel().getSelectedItem());
        source.setRepositoryLink(sourceURLTextField.getText());

        dxmlData.performValidation();
    }

    /**
     * If the next button is pressed.
     */
    public void next() {
        switch (paneState) {
        case PROJECT_INFO:
            if (isProjectInfoPaneValid()) {
                middleStackPane.getChildren().clear();
                middleStackPane.getChildren().add(compilationPane);
                backButton.setDisable(false);
                nextButton.setDisable(!isCompilationPaneValid());
                paneState = DXMLProjectPaneState.COMPILATION;
            } else {
                nextButton.setDisable(true);
            }
            break;
        case COMPILATION:
            if (isCompilationPaneValid()) {
                middleStackPane.getChildren().clear();
                middleStackPane.getChildren().add(layoutPane);
                backButton.setDisable(false);
                nextButton.setDisable(!isLayoutPaneValid());
                paneState = DXMLProjectPaneState.LAYOUT;
            } else {
                nextButton.setDisable(true);
            }
            break;
        case LAYOUT:
            if (isLayoutPaneValid()) {
                middleStackPane.getChildren().clear();
                middleStackPane.getChildren().add(sourcePane);
                backButton.setDisable(false);
                nextButton.setDisable(!isSourcePaneValid());
                nextButton.setText("Finish");
                paneState = DXMLProjectPaneState.SOURCE;
            } else {
                nextButton.setDisable(true);
            }
            break;
        case SOURCE:
            if (isSourcePaneValid()) {
                generateDXML();
                wasClosed = false;
                exitWizard();
            } else {
                nextButton.setDisable(true);
            }
            break;
        default:
            throw new RuntimeException("Unexpected pane enumeration in the DXML project wizard.");
        }
    }

    /**
     * If the back button is pressed.
     */
    public void back() {
        switch (paneState) {
        case PROJECT_INFO:
            log.error("Should not have invoked back button on " + paneState.name());
            break;
        case COMPILATION:
            middleStackPane.getChildren().clear();
            middleStackPane.getChildren().add(projectInfoPane);
            backButton.setDisable(true);
            nextButton.setDisable(!isProjectInfoPaneValid());
            paneState = DXMLProjectPaneState.PROJECT_INFO;
            projectInfoTextUpdated();
            break;
        case LAYOUT:
            middleStackPane.getChildren().clear();
            middleStackPane.getChildren().add(compilationPane);
            nextButton.setDisable(!isCompilationPaneValid());
            paneState = DXMLProjectPaneState.COMPILATION;
            compilationTextUpdated();
            break;
        case SOURCE:
            middleStackPane.getChildren().clear();
            middleStackPane.getChildren().add(layoutPane);
            nextButton.setDisable(!isLayoutPaneValid());
            nextButton.setText("Next >");
            paneState = DXMLProjectPaneState.LAYOUT;
            layoutTextUpdated();
            break;
        default:
            throw new RuntimeException("Unexpected pane enumeration in the DXML project wizard.");
        }
    }

    /**
     * Does directory browsing for the user to supply a directory.
     */
    public void browseFolders() {
        if (stage == null) {
            log.error("Attempting to browse folders with a null parent stage.");
            return;
        }

        // Lock on to the main stage and wait until we get a directory.
        DirectoryChooser directoryChooser = new DirectoryChooser();
        File selectedDirectory = directoryChooser.showDialog(stage);
        if (selectedDirectory != null) {
            projectFolderTextfield.setText(selectedDirectory.getAbsolutePath());
            projectFolderTextfield.requestFocus();
            projectFolderTextfield.end(); // Move the caret to the end so the user can see the important part.
            projectInfoTextUpdated();
        }
    }

    /**
     * Checks if the underlying fields are valid.
     * 
     * @return
     *       True if they are, false if not.
     */
    private boolean isProjectInfoPaneValid() {
        return Files.exists(Paths.get(projectFolderTextfield.getText()))
                && !projectNameTextfield.getText().isEmpty() && !projectAuthorsTextfield.getText().isEmpty();
    }

    /**
     * Checks to see if the next button should be available.
     */
    private void projectInfoTextUpdated() {
        nextButton.setDisable(!isProjectInfoPaneValid());
    }

    /**
     * Checks if the underlying fields are valid.
     * 
     * @return
     *       True if they are, false if not.
     */
    private boolean isCompilationPaneValid() {
        return !compilationFileNameTextField.getText().isEmpty() && !compilationVersionTextField.getText().isEmpty()
                && DXMLCompilation.VERSION_PATTERN.matcher(compilationVersionTextField.getText()).matches();
    }

    /**
     * Checks to see if the next button should be available.
     */
    private void compilationTextUpdated() {
        nextButton.setDisable(!isCompilationPaneValid());
    }

    /**
     * Checks if the underlying fields are valid.
     * 
     * @return
     *       True if they are, false if not.
     */
    private boolean isLayoutPaneValid() {
        return subFolderPattern.matcher(layoutSourceTextField.getText()).matches()
                && subFolderPattern.matcher(layoutResourcesTextField.getText()).matches()
                && subFolderPattern.matcher(layoutBuildTextField.getText()).matches();
    }

    /**
     * Checks to see if the next button should be available.
     */
    private void layoutTextUpdated() {
        nextButton.setDisable(!isLayoutPaneValid());
    }

    /**
     * Checks if the underlying fields are valid.
     * 
     * @return
     *       True if they are, false if not.
     */
    private boolean isSourcePaneValid() {
        return true; // All good for now.
    }

    //   /**
    //    * Attempts to download dependencies.
    //    * 
    //    * @return
    //    *       True if all downloaded (or there is none), false if one or more 
    //    *       failed.
    //    */
    //   private boolean downloadDependenciesIfNeeded() {
    //      // Go through each dependency item.
    //      for (String listItem : dependencyListView.getItems()) {
    //         // If it's a URL, we want to attempt to download tat.
    //         if (listItem.startsWith("URL: ")) {
    //            // Remove the URL: prefix.
    //            String urlString = listItem.substring(5);
    //            System.out.println("Processing '" + urlString + "'");
    //            
    //            URL url = null;
    //            int numBytes = -1;
    //            
    //            // Get the URL.
    //            try {
    //               url = new URL(urlString);
    //            } catch (MalformedURLException e) {
    //               System.out.println("Bad URL");
    //               return false;
    //            }
    //            
    //            // Try to open the connection to get how many bytes the file is.
    //            try {
    //               HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
    //               
    //               // If the connection is not okay, we can't do much.
    //               if (httpURLConnection.getResponseCode() != HttpURLConnection.HTTP_OK) {
    //                  System.out.println("HTTP connection is not viable.");
    //                  return false;
    //               } else {
    //                  System.out.println("Good HTTP connection");
    //               }
    //               
    //               // Since it's valid, record how long the data is.
    //               numBytes = httpURLConnection.getContentLength();
    //               System.out.println("This file is " + numBytes + " bytes");
    //            } catch (IOException e1) {
    //               e1.printStackTrace();
    //               System.out.println("URLConnectionException IO");
    //               return false;
    //            }
    //            
    //            // Now that we know how long it is, stream it.
    //            ByteArrayOutputStream baos = new ByteArrayOutputStream(numBytes);
    //            byte[] buffer = new byte[2048];
    //            int byteReadCount = -1;
    //            int totalBytesRead = 0;
    //            try (InputStream is = url.openStream()) {
    //               while ((byteReadCount = is.read(buffer)) != -1) {
    //                  baos.write(buffer, 0, byteReadCount);
    //                  totalBytesRead += byteReadCount;
    //               }
    //               System.out.println("Read " + totalBytesRead);
    //            } catch (IOException e) {
    //               e.printStackTrace();
    //               System.out.println("IOException");
    //               return false;
    //            }
    //            
    //            // Take our read data and store it in memory for the user.
    //            // TODO
    //            byte[] data = baos.toByteArray();
    //            System.out.println(data.length);
    //         }
    //      }
    //      return true;
    //   }

    @Override
    public Pane getRootPane() {
        return rootBorderPane;
    }

    public DXML getDxmlData() {
        return dxmlData;
    }

    public boolean wasClosedByUser() {
        return wasClosed;
    }

    public void setStage(Stage stage) {
        this.stage = stage;
    }

    public void setParentStage(Stage stage) {
        this.parentStage = stage;
    }
}

/**
 * An enumeration of what pane the project is on.
 */
enum DXMLProjectPaneState {
    PROJECT_INFO, COMPILATION, LAYOUT, SOURCE;
}