Java tutorial
/* * 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; }