A Copy Files App using JavaFX - Java Source Code
FileTreeView.java
package com.javaquizplayer.examples.copyfilesapp; import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.layout.VBox; import javafx.scene.layout.HBox; import javafx.scene.control.TreeView; import javafx.scene.control.TreeItem; import javafx.scene.control.Button; import javafx.scene.control.SelectionMode; import javafx.scene.control.Tooltip; import javafx.scene.control.CheckBoxTreeItem; import javafx.scene.control.cell.CheckBoxTreeCell; import javafx.scene.control.CheckBoxTreeItem.TreeModificationEvent; import javafx.stage.DirectoryChooser; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.collections.ObservableList; import javafx.application.Platform; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Set; import java.util.HashSet; import java.util.List; import java.util.ArrayList; import java.util.logging.Logger; /* * Main GUI class for this app. * Builds a tree view of the files for the selected file system root * directory. Has functions to expand, collapse the file tree and check * the files and directories for copy and open the copy files dialog. */ public class FileTreeView { private TreeView<Path> tree; private Button copyBtn; private CopyDialog copyDialog; private Path rootDir; // The chosen root or source directory // All file and directories that are checked are stored here private Set<Path> checkedItems; // Indecies of selected directory nodes, used with expand or collapse // of specific directory nodes. private List<Integer> selectedIndecies; // Flag to indicate if the root directory node is in expanded or // collapsed state. This is used with the tree expand/collapse toggle // button. In case the node is collapsed it is expanded and vice-versa. private boolean isExpanded; private static final String DEFAULT_DIRECTORY = System.getProperty("user.dir"); // or "user.home" private static Logger logger; /* * Constructor builds and displays the app's main view. */ public FileTreeView(Stage primaryStage) { logger = Logger.getLogger("copy_app_logger"); copyDialog = new CopyDialog(); checkedItems = new HashSet<Path>(); Button expandBtn = new Button('\u2039' + " " + '\u203A'); expandBtn.setTooltip(new Tooltip("Expand or collapse selected tree items")); expandBtn.setOnAction(e -> expandOrCollapseSelectedItemsRoutine()); Button expandAllBtn = new Button('\u00AB' + " " + '\u00BB'); expandAllBtn.setTooltip(new Tooltip("Expand or collapse entire tree")); expandAllBtn.setOnAction(e -> expandOrCollapseTreeRoutine()); HBox hb1 = new HBox(15); hb1.setAlignment(Pos.CENTER); hb1.getChildren().addAll(expandAllBtn, expandBtn); Button sourceDirBtn = new Button("Source directory..."); sourceDirBtn.setTooltip(new Tooltip("Set a new root directory")); sourceDirBtn.setOnAction(e -> { chooseSourceDirectory(primaryStage); tree.setRoot(getRootItem()); }); copyBtn = new Button("Copy dialog..."); copyBtn.setTooltip(new Tooltip("Initiate the copy action: opens copy dialog")); copyBtn.setDisable(true); copyBtn.setOnAction(e -> copyDialogRoutine()); HBox hb2 = new HBox(15); hb2.setAlignment(Pos.CENTER); hb2.getChildren().addAll(sourceDirBtn, copyBtn); VBox vb = new VBox(20); vb.setPadding(new Insets(20)); primaryStage.setScene(new Scene(vb, 800, 600)); // w, h primaryStage.setTitle("Copy Files App"); primaryStage.show(); chooseSourceDirectory(primaryStage); vb.getChildren().addAll(buildFileTreeView(), hb1, hb2); } /* * Opens the directory chooser with an initial root directory. * Allows the user to use the same, or select another one. * In case the chooser is cancelled a root directory is derived. */ private void chooseSourceDirectory(Stage primaryStage) { DirectoryChooser chooser = new DirectoryChooser(); chooser.setTitle("Select a source directory"); chooser.setInitialDirectory(getInitialDirectory().toFile()); File chosenDir = chooser.showDialog(primaryStage); determineRootDirectory(chosenDir); copyBtn.setDisable(true); checkedItems.clear(); isExpanded = false; logger.info("Root dir chosen: " + rootDir); } /* * Initial root directory set for the directory chooser. */ private Path getInitialDirectory() { return (rootDir == null) ? Paths.get(DEFAULT_DIRECTORY) : rootDir; } /* * The root directory for the tree is derived here. * If a directory is chosen it is the root directory. * If not, the previous root directory is used. In case of no * previous directory the DEFAULT_DIRECTORY is the root. */ private void determineRootDirectory(File chosenDir) { rootDir = (chosenDir != null) ? chosenDir.toPath() : getInitialDirectory(); } /* * Creates and returns the root item for the root directory. */ private FileTreeItem getRootItem() { FileTreeItem rootItem = new FileTreeItem(rootDir); rootItem.setIndependent(false); rootItem.addEventHandler( CheckBoxTreeItem.<Path>checkBoxSelectionChangedEvent(), (TreeModificationEvent<Path> e) -> handleItemCheckedEvent(e)); return rootItem; } /* * Builds and returns the tree view for the given root item. */ private TreeView buildFileTreeView() { tree = new TreeView<Path>(getRootItem()); tree.setPrefHeight(600.0d); tree.setTooltip(new Tooltip("Expand (all) or collapse the tree, select items and copy...")); tree.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); tree.setCellFactory((TreeView<Path> t) -> new TreeCellImpl()); return tree; } /* * Event handler for the tree item checked or unchecked event. * On checking or unchecking an item the related items are stored * or removed from a Set collection respectively. These items in * the collection are used for copying. */ private void handleItemCheckedEvent(TreeModificationEvent<Path> e) { FileTreeItem item = (FileTreeItem) e.getTreeItem(); if (item.isSelected() || item.isIndeterminate()) { collectCheckedItems(item); } else { removeCollectedCheckedItems(item); } copyBtn.setDisable((checkedItems.isEmpty()) ? true : false); } private void collectCheckedItems(FileTreeItem item) { if (item.isSelected() || item.isIndeterminate()) { // A file or directory is checked, or if a directory // with files has some items checked: collect these items checkedItems.add(item.getValue()); } item.getChildren().forEach(t -> collectCheckedItems((FileTreeItem) t)); } private void removeCollectedCheckedItems(FileTreeItem item) { if (! item.isSelected()) { // A file or directory is unchecked: remove these items checkedItems.remove(item.getValue()); } item.getChildren() .forEach(t -> removeCollectedCheckedItems((FileTreeItem) t)); } /* * Expand or collapse the entire file tree. */ private void expandOrCollapseTreeRoutine() { FileTreeItem rootItem = (FileTreeItem) tree.getRoot(); expandOrCollapseTree(rootItem); isExpanded = (isExpanded) ? false : true; } private void expandOrCollapseTree(FileTreeItem item) { if (! item.isLeaf()) { item.setExpanded((isExpanded) ? false : true); item.getChildren().forEach(t -> expandOrCollapseTree((FileTreeItem) t)); } } /* * Expand or collapse the selected items (directories) in the file tree. * NOTE: this is not the check box select, it is the item selection. * An item is highlighted when it is selected. Multiple items can * be selected at a time. */ private void expandOrCollapseSelectedItemsRoutine() { selectedIndecies = new ArrayList<Integer>(); ObservableList<TreeItem<Path>> items = tree.getSelectionModel().getSelectedItems(); items.forEach(t -> expandOrCollapseSelectedItems((FileTreeItem) t)); setSelectedItems(); } private void expandOrCollapseSelectedItems(FileTreeItem item) { if ((item != null) && (! item.isLeaf())) { selectedIndecies.add(tree.getRow(item)); item.setExpanded((item.isExpanded()) ? false : true); item.getChildren() .forEach(t -> expandOrCollapseSelectedItems((FileTreeItem) t)); } } /* * Select the items (retain the selection) which were in selected * state before the items expand or collapse action. */ private void setSelectedItems() { if (selectedIndecies.isEmpty()) { return; // there is no selection, can't expand or collapse } // One or more items selected int firstIndex = selectedIndecies.get(0); int [] remaining = new int [selectedIndecies.size() - 1]; for (int j = 0, i = 1; j < remaining.length; j++) { remaining [j] = selectedIndecies.get(i); i++; } tree.getSelectionModel().selectIndices(firstIndex, remaining); } /* * Copy dialog button action routine. * Opens the CopyDialog modal dialog. */ private void copyDialogRoutine() { logger.info("Copy dialog..."); FileTreeItem rootItem = (FileTreeItem) tree.getRoot(); Path sourceDir = rootItem.getValue(); copyDialog.create(sourceDir, checkedItems); } /* * Inner class to render check boxes with file names for the tree items. */ private class TreeCellImpl extends CheckBoxTreeCell<Path> { @Override public void updateItem(Path path, boolean empty) { super.updateItem(path, empty); if (empty) { setText(null); } else { if (path != null) { String s = path.getFileName().toString(); setText(s); } } } } }
FileTreeItem.java
package com.javaquizplayer.examples.copyfilesapp; import javafx.scene.control.TreeItem; import javafx.scene.control.CheckBoxTreeItem; import javafx.collections.ObservableList; import javafx.collections.FXCollections; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.stream.Stream; import java.util.stream.Collectors; /* * This class is a TreeItem for the given file Path with a checkbox. It does * this by overriding the TreeItem's getChildren() and the isLeaf() methods. * Note that CheckBoxTreeItem extends TreeItem. */ public class FileTreeItem extends CheckBoxTreeItem<Path> { // Cache whether the file is a leaf or not. A file is a leaf if // it is not a directory. The isLeaf() is called often, and doing // the actual check on Path is expensive. private boolean isLeaf; // Do the children and leaf testing only once, and then set these // booleans to false so that we do not check again during this run. private boolean isFirstTimeChildren = true; private boolean isFirstTimeLeaf = true; /* * Constructor. * The parameter is the root or source input directory path used * to build the file tree with the TreeView control. */ public FileTreeItem(Path path) { super(path); } @Override public boolean isLeaf() { if (isFirstTimeLeaf) { isFirstTimeLeaf = false; Path path = getValue(); isLeaf = Files.isRegularFile(path); } return isLeaf; } /* * Returns a list that contains the child TreeItems belonging to the TreeItem. */ @Override public ObservableList<TreeItem<Path>> getChildren() { if (isFirstTimeChildren) { isFirstTimeChildren = false; // First getChildren() call, so we actually go off and // determine the children of the file contained in this TreeItem. super.getChildren().setAll(buildChildren(this)); } return super.getChildren(); } private ObservableList<TreeItem<Path>> buildChildren( CheckBoxTreeItem<Path> treeItem) { Path path = treeItem.getValue(); if ((path != null) && (Files.isDirectory(path))) { try(Stream<Path> pathStream = Files.list(path)) { return pathStream .map(p -> new FileTreeItem(p)) .collect(Collectors.toCollection(() -> FXCollections.observableArrayList())); } catch(IOException e) { throw new UncheckedIOException(e); } } return FXCollections.emptyObservableList(); } }
CopyDialog.java
package com.javaquizplayer.examples.copyfilesapp; import javafx.scene.layout.VBox; import javafx.scene.layout.HBox; import javafx.scene.control.Dialog; import javafx.scene.control.TextArea; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; import javafx.scene.control.Tooltip; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.CheckBox; import javafx.scene.control.ProgressBar; import javafx.scene.Scene; import javafx.scene.text.Font; import javafx.stage.Stage; import javafx.stage.Modality; import javafx.stage.DirectoryChooser; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.collections.FXCollections; import javafx.concurrent.Task; import javafx.application.Platform; import java.util.Set; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.logging.Logger; import java.util.stream.Collectors; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Files; import java.nio.file.FileVisitResult; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; import java.nio.file.FileAlreadyExistsException; import java.nio.file.attribute.BasicFileAttributes; /* * Constructs the copy files dialog. This is invoked from the app's main GUI. * This dialog captures the info to perform the copy of the selected files * in the main view: the target directory, the file filters and an option to * create a ZIP file. Starts the copy process: the file filters are applied * to the selected files and the filtered files are copied to the target. The * process can be cancelled if needed. The process progress is viewed thru a * progress bar and the status is logged to the status area. */ public class CopyDialog { private TextArea statusArea; private Button selectTargetBtn; private Button filtersBtn; private Button copyBtn; private Button cancelBtn; private Button closeBtn; private CheckBox zipCheckBox; private ProgressBar progressBar; private FileFilterDialog fileFiltersDialog; private FileFilters fileFilters; // The root or the source directory input from the main GUI with the // file tree view. private Path sourceDir; // The target or destination directory to which the files are copied to. // This is obtained from a directory chooser in this dialog. private Path targetDir; // The files copy is performed in a background thread by this Task object. // See copyRoutine() method. private Task<Void> copyTask; // Counters for total files and directories that are actually copied. // These are used to show the status after the copy task is complete. private int copiedFilesCount; private int copiedDirsCount; private static final String DEFAULT_DIRECTORY = System.getProperty("user.dir"); // or "user.home" private static Logger logger; /* * Constructor. * Creates a copy of the FileFilterDialog. * Obtains the logger and configures it with the TextAreaLogHandler. */ public CopyDialog() { logger = Logger.getLogger("copy_app_logger"); statusArea = getTextArea(); TextAreaLogHandler handler = new TextAreaLogHandler(statusArea); logger.addHandler(handler); fileFiltersDialog = new FileFilterDialog(); } /* * Constructs the GUI for the Copy dialog. */ public void create(Path sourceDir, Set<Path> selectedFiles) { this.sourceDir = sourceDir; Stage dialog = new Stage(); dialog.setResizable(false); dialog.setTitle("Copy Files"); dialog.initModality(Modality.APPLICATION_MODAL); dialog.setOnCloseRequest(e -> e.consume()); // disable Close (x) button Platform.runLater(() -> statusArea.setText("")); selectTargetBtn = new Button("Target directory..."); selectTargetBtn.setTooltip(new Tooltip("Select a target directory")); selectTargetBtn.setOnAction(e -> chooseTargetDirectory()); filtersBtn = new Button("Filters..."); filtersBtn.setTooltip(new Tooltip("Apply file filters")); filtersBtn.setOnAction(e -> getFilters()); filtersBtn.setDisable(true); zipCheckBox = new CheckBox("Create ZIP file"); zipCheckBox.setDisable(true); copyBtn = new Button("Copy files..."); copyBtn.setTooltip(new Tooltip("Copy files to target directory")); copyBtn.setOnAction(e -> copyRoutine(selectedFiles)); copyBtn.setDisable(true); cancelBtn = new Button("Cancel copy"); cancelBtn.setTooltip(new Tooltip("Cancel or abort the copy process")); cancelBtn.setOnAction(e -> { if (copyTask != null) { copyTask.cancel(); } }); cancelBtn.setDisable(true); closeBtn = new Button("Close"); closeBtn.setTooltip(new Tooltip("Close the dialog")); closeBtn.setOnAction(e -> dialog.close()); HBox btnHb = new HBox(15); btnHb.setAlignment(Pos.CENTER); /* * The ZIP file create option checkbox is available with the * Windows operating system only. */ if (System.getProperty("os.name").toLowerCase().contains("windows")) { btnHb.getChildren().addAll(selectTargetBtn, filtersBtn, zipCheckBox, copyBtn, cancelBtn, closeBtn); } else { btnHb.getChildren().addAll(selectTargetBtn, filtersBtn, copyBtn, cancelBtn, closeBtn); } progressBar = new ProgressBar(); progressBar.setPrefWidth(600.0d); progressBar.setTooltip(new Tooltip("Copy files process progress")); HBox statusHb = new HBox(); statusHb.setAlignment(Pos.CENTER); statusHb.getChildren().addAll(progressBar); VBox vb = new VBox(20); vb.setPadding(new Insets(15, 15, 5, 15)); vb.getChildren().addAll(statusArea, statusHb, btnHb); dialog.setScene(new Scene(vb)); String initialText = "* Copy Files to a Target Directory * \n" + "Total directories (includes root) and files selected: " + selectedFiles.size() + " " + "\nSource directory: " + sourceDir.toString() + " " + "\n\nSelect a target directory, apply file filters and copy.\n"; logger.info(initialText); dialog.showAndWait(); // this shows a modal window } private TextArea getTextArea() { TextArea textArea = new TextArea(); textArea.setTooltip(new Tooltip("Status message area")); textArea.setPrefRowCount(14); textArea.setPrefColumnCount(60); textArea.setEditable(false); textArea.setFont(new Font("Verdana", 16)); return textArea; } /* * Opens the directory chooser and lets the user select a * target directory for copying the selected files. The * directory is verified if it is valid. */ private void chooseTargetDirectory() { DirectoryChooser chooser = new DirectoryChooser(); chooser.setTitle("Select a target directory"); chooser.setInitialDirectory(new File(DEFAULT_DIRECTORY)); File chosenDir = chooser.showDialog(null); targetDir = (chosenDir == null) ? null : chosenDir.toPath(); if (! verifyDirectories()) { return; } logger.info("Target directory: " + targetDir.toString()); progressBar.progressProperty().unbind(); progressBar.setProgress(0); fileFilters = null; zipCheckBox.setDisable(false); copyBtn.setDisable(false); filtersBtn.setDisable(false); filtersBtn.requestFocus(); } /* * Checks if the target directory path is not the same as * that of the source path, or the target is not within the * source directory structure; shows an alert message. */ private boolean verifyDirectories() { if (targetDir == null) { showAlertDialog("No directory selected!"); return false; } if ((sourceDir.equals(targetDir)) || (targetDir.startsWith(sourceDir))) { showAlertDialog("Source and target directories are same, or " + "the target is within the source."); return false; } if (targetDir.toFile().list().length > 0) { logger.warning("The target directory is not empty."); } return true; } /* * Displays a modal alert with the supplied message. */ private void showAlertDialog(String msg) { Alert alert = new Alert(AlertType.NONE); alert.setTitle("Files Copy"); alert.getDialogPane().getButtonTypes().add(ButtonType.OK); alert.setContentText(msg); alert.show(); } /* * Displays the file filters dialog and captures user input. Gets * the selected file filter options as an instance of FileFilters. */ private void getFilters() { Dialog<FileFilters> dialog = fileFiltersDialog.create(); Optional<FileFilters> result = dialog.showAndWait(); if (result.isPresent()) { fileFilters = result.get(); if (fileFilters.getFileTypes().isEmpty()) { // In case there is no selection in the file types list, // which is possible with a ListView, the value is set. fileFilters.setFileTypes(FXCollections.observableArrayList("All")); } } logger.info("File filters: " + fileFilters.toString()); zipCheckBox.requestFocus(); } /* * Routine for the Copy files button action. * 1. Applies the file filters to the selected files. * 2. Copies the filtered files to target directory. * 3. Creates a ZIP file if the option is selected. * These tasks are performed as a JavaFX concurrent Task. At end, * a status (Succeeded, Failed/exception or Cancelled) is displayed * in the status message area. */ private void copyRoutine(Set<Path> inputSelectedFiles) { copiedFilesCount = 0; copiedDirsCount = 0; copyTask = new Task<Void>() { int currentCounter; @Override protected Void call() throws Exception { logger.info("Copying files."); Platform.runLater(() -> { copyBtn.setDisable(true); closeBtn.setDisable(true); cancelBtn.setDisable(false); filtersBtn.setDisable(true); zipCheckBox.setDisable(true); selectTargetBtn.setDisable(true); }); Set<Path> filteredFiles = applyFileFilters(inputSelectedFiles); Map<Boolean, List<Path>> countsMap = filteredFiles.stream() .collect(Collectors.partitioningBy(p -> Files.isDirectory(p))); int dirsCount = countsMap.get(true).size() - 1; // minus root dir int filesCount = countsMap.get(false).size(); logger.info("Filters applied. " + "Directories [" + ((dirsCount < 0) ? 0 : dirsCount) + "], " + "Files [" + filesCount + "]."); Thread.sleep(100); // pause for n milliseconds logger.info("Copy in progress..."); /* * Walks the source file tree and copies the filtered source * files to the target directory. The directories and files are * copied. In case of any existing directories or files in the * target, they are replaced. */ Files.walkFileTree(sourceDir, new SimpleFileVisitor<Path>() { /* * Copy the directories. */ @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { if (isCancelled()) { // Task's isCancelled() method returns true // when its cancel() is executed; in this app // when the Cancel copy button is clicked. // Here, the files copy is terminated. return FileVisitResult.TERMINATE; } if (! filteredFiles.contains(dir)) { return FileVisitResult.SKIP_SUBTREE; } Path target = targetDir.resolve(sourceDir.relativize(dir)); try { Files.copy(dir, target); copiedDirsCount++; // Updates the Progess bar using the Task's // updateProgress(workDone, max) method. updateProgress(++currentCounter, dirsCount+filesCount); } catch (FileAlreadyExistsException e) { if (! Files.isDirectory(target)) { throw e; } } return FileVisitResult.CONTINUE; } /* * Copy the files. */ @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (isCancelled()) { // Task's isCancelled() method // terminates the files copy. return FileVisitResult.TERMINATE; } if (filteredFiles.contains(file)) { Files.copy(file, targetDir.resolve(sourceDir.relativize(file)), StandardCopyOption.REPLACE_EXISTING); copiedFilesCount++; // Updates the Progess bar using the Task's // updateProgress(workDone, max) method. updateProgress(++currentCounter, dirsCount+filesCount); } return FileVisitResult.CONTINUE; } }); if (zipCheckBox.isSelected()) { if (copiedFilesCount > 0) { logger.info("Creating ZIP file, wait... "); Thread.sleep(100); String zipFile = ZipFileCreater.zip(targetDir); logger.info("ZIP file created: " + zipFile); } else { logger.info("Cannot create ZIP file with files count = 0"); } } return null; } }; // end copyTask class progressBar.progressProperty().bind(copyTask.progressProperty()); new Thread(copyTask).start(); // Run the copy task // Calling event handlers as task's state is transitioned to // SUCCEEDED, FAILED or CANCELLED. copyTask.setOnFailed(e -> { Throwable t = copyTask.getException(); String message = (t != null) ? t.toString() : "Unknown Exception!"; logger.info("There was an error during the copy process:"); logger.info(message); doTaskEventCloseRoutine(copyTask); //t.printStackTrace(); }); copyTask.setOnCancelled(e -> { logger.info("Copy is cancelled by user."); doTaskEventCloseRoutine(copyTask); }); copyTask.setOnSucceeded(e -> { logger.info("Copy completed. " + "Directories copied [" + ((copiedDirsCount < 1) ? 0 : copiedDirsCount) + "], " + "Files copied [" + copiedFilesCount + "]"); doTaskEventCloseRoutine(copyTask); }); } private void doTaskEventCloseRoutine(Task copyTask) { logger.info("Status: " + copyTask.getState() + "\n"); logger.info("Select a target directory, apply file filters and copy."); Platform.runLater(() -> { selectTargetBtn.setDisable(false); closeBtn.setDisable(false); cancelBtn.setDisable(true); }); } /* * Sets the file filters to its default value in case the filters dialog * is not opened at all. Otherwise the already set value is used. Apply * the file filters; the filtered files are returned as a Set collection. */ private Set<Path> applyFileFilters(Set<Path> selectedFiles) throws IOException { if (fileFilters == null) { fileFilters = FileFilters.getDefault(); logger.info("File filters: " + fileFilters.toString()); } return new FileFilterApplication().apply(sourceDir, selectedFiles, fileFilters); } }
DateOption.java
package com.javaquizplayer.examples.copyfilesapp; import java.util.Map; import java.util.HashMap; import java.util.EnumSet; import java.util.stream.Collectors; import java.util.function.Function; /* * Enum class represents the date file filter options. * Also see FileFilters and FileFiltersDialog. */ public enum DateOption { ALL_DAYS, TODAY, LAST_7_DAYS, LAST_30_DAYS; @Override public String toString() { return getFormattedString(super.toString()); } /* * Formats enum's string, for example: from LAST_7_DAYS to: Last 7 days. * Replaces the underscore ("_") with a blank space. Changes the case to * first letter upper and remaining lower case. */ private static String getFormattedString(String input) { String s = input.toLowerCase(); s = s.replace("_", " "); return (s.substring(0, 1).toUpperCase() + s.substring(1, s.length())); } /* Map with formatted string as key and enum constant as value */ private static Map<String, DateOption> map; /* Initially, populates the map */ static { map = EnumSet.allOf(DateOption.class) .stream() .collect(Collectors.toMap( d -> getFormattedString(d.toString()), Function.identity())); } /* * The lookup for the map. Returns the enum constant for the * given string representing the constant. */ public static DateOption lookup(String s) { return map.get(s); } }
FileFilters.java
package com.javaquizplayer.examples.copyfilesapp; import javafx.collections.FXCollections; import javafx.collections.ObservableList; /* * This class represents the file filters data. This data is * captured in the FileFiltersDialog and is applied in the files * copy routine. */ public class FileFilters { private boolean allFiles; private DateOption dateOpt; private ObservableList<String> fileTypes; /* * List of file extensions for selection. "All" specifies that select * all file types. The "" (empty string) specifies that the file has no * extension. */ public static final String [] FILE_EXTENSIONS = {"All", "java", "class", "txt", "doc", "docx", "xls", "xlsx", "ppt", "png", "jpg", "pdf", "jar", "exe", "html", "xhtml", "htm", "mp3", "wmv", ""}; /* * Constructor with default values. */ public FileFilters() { allFiles = false; dateOpt = DateOption.ALL_DAYS; fileTypes = FXCollections.observableArrayList("All"); } /* * Returns an instance of FileFilters with following options: * All files for all days. */ public static FileFilters getDefault() { return new FileFilters(); } public void setAllFiles(boolean b) { allFiles = b; } public boolean getAllFiles() { return allFiles; } public void setDateOption(DateOption d) { dateOpt = d; } public DateOption getDateOption() { return dateOpt; } public void setFileTypes(ObservableList<String> types) { fileTypes = types; } public ObservableList<String> getFileTypes() { return fileTypes; } @Override public String toString() { return dateOpt.toString() + ", " + fileTypes.toString(); } }
FileFilterDialog.java
package com.javaquizplayer.examples.copyfilesapp; import javafx.scene.layout.VBox; import javafx.scene.layout.HBox; import javafx.scene.control.Dialog; import javafx.scene.control.CheckBox; import javafx.scene.control.ToggleGroup; import javafx.scene.control.RadioButton; import javafx.scene.control.ListView; import javafx.scene.control.SelectionMode; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; import javafx.scene.control.ButtonBar.ButtonData; import javafx.geometry.Orientation; import javafx.geometry.Insets; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; /* * This class has a single method which builds a dialog to capture file * filter options. The method returns a dialog with an instance of * FileFilters with the selected input or a default instance in case the * dialog is cancelled. */ public class FileFilterDialog { public Dialog<FileFilters> create() { Dialog<FileFilters> dialog = new Dialog<>(); dialog.setTitle("File Filters"); dialog.setResizable(false); CheckBox allCheckBox = new CheckBox("Select all files"); ToggleGroup radioGroup = new ToggleGroup(); RadioButton radio1 = new RadioButton(DateOption.ALL_DAYS.toString()); radio1.setSelected(true); RadioButton radio2 = new RadioButton(DateOption.TODAY.toString()); RadioButton radio3 = new RadioButton(DateOption.LAST_7_DAYS.toString()); RadioButton radio4 = new RadioButton(DateOption.LAST_30_DAYS.toString()); radio1.setToggleGroup(radioGroup); radio2.setToggleGroup(radioGroup); radio3.setToggleGroup(radioGroup); radio4.setToggleGroup(radioGroup); HBox radioHb = new HBox(15); radioHb.getChildren().addAll(radio1, radio2, radio3, radio4); ListView<String> fileTypesList = new ListView<>(); fileTypesList.setPrefHeight(50.0d); fileTypesList.setItems(FXCollections.observableArrayList(FileFilters.FILE_EXTENSIONS)); fileTypesList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); fileTypesList.setOrientation(Orientation.HORIZONTAL); allCheckBox.selectedProperty().addListener( (ObservableValue<? extends Boolean> ov, Boolean oldVal, Boolean newVal) -> { fileTypesList.getSelectionModel().clearSelection(); fileTypesList.getSelectionModel().selectFirst(); fileTypesList.setDisable(newVal); radio1.setSelected(true); radio1.setDisable(newVal); radio2.setDisable(newVal); radio3.setDisable(newVal); radio4.setDisable(newVal); }); VBox vb = new VBox(20); vb.setPadding(new Insets(20)); vb.getChildren().addAll(allCheckBox, radioHb, fileTypesList); ButtonType okButtonType = new ButtonType("Okay", ButtonData.OK_DONE); dialog.getDialogPane().getButtonTypes().add(okButtonType); Button okBtn = (Button) dialog.getDialogPane().lookupButton(okButtonType); dialog.setResultConverter((ButtonType b) -> { if (b == okButtonType) { FileFilters ff = new FileFilters(); ff.setAllFiles(allCheckBox.isSelected()); String s = ((RadioButton) radioGroup.getSelectedToggle()).getText(); ff.setDateOption(DateOption.lookup(s)); ff.setFileTypes(fileTypesList.getSelectionModel().getSelectedItems()); return ff; } return FileFilters.getDefault(); }); dialog.getDialogPane().setContent(vb); fileTypesList.getSelectionModel().selectFirst(); return dialog; } }
FileFilterApplication.java
package com.javaquizplayer.examples.copyfilesapp; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Files; import java.nio.file.FileVisitResult; import java.nio.file.SimpleFileVisitor; import java.util.Set; import java.util.HashSet; import java.nio.file.attribute.BasicFileAttributes; import java.time.ZoneId; import java.time.Instant; import java.time.LocalDate; /* * Class has a single method which returns the collection of * Path objects after the file filters are applied. Additionally, * any empty directories are removed from the filtered collection. * The input is the selected files, source direcory and the * file filters. */ public class FileFilterApplication { public FileFilterApplication() { } public Set<Path> apply(Path sourceDir, Set<Path> selectedFiles, FileFilters filters) throws IOException { Set<Path> filteredFiles = new HashSet<>(); Files.walkFileTree(sourceDir, new SimpleFileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { if (! selectedFiles.contains(dir)) { // Not a selected directory, skip it return FileVisitResult.SKIP_SUBTREE; } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (selectedFiles.contains(file) && applyFileTypeFilter(filters, file) && applyDateOptionFilter(filters, file)) { // Add selected files that match the // file filter criteria filteredFiles.add(file); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { if (dirHasFiles(dir, filteredFiles)) { // Add directories with files in it filteredFiles.add(dir); } return FileVisitResult.CONTINUE; } }); return filteredFiles; } /* * Check if the input dir has any files in it that are already * filtered. Note this check is not on the file system. */ private boolean dirHasFiles(Path inDir, Set<Path> list) { return list .stream() .anyMatch(p -> ((!p.equals(inDir)) && p.startsWith(inDir))); } private boolean applyFileTypeFilter(FileFilters filters, Path file) { return filters.getFileTypes().contains("All") || filters.getFileTypes().contains(getFileExtension(file)); } private String getFileExtension(Path file) { String fileName = file.getFileName().toString(); int ix = fileName.lastIndexOf("."); return (ix == -1) ? "" : fileName.substring(ix + 1); } private boolean applyDateOptionFilter(FileFilters filters, Path file) throws IOException { boolean returnValue = false; switch (filters.getDateOption()) { case TODAY: returnValue = getFileDate(file).isEqual(LocalDate.now()); break; case LAST_7_DAYS: returnValue = getFileDate(file).isAfter(LocalDate.now().minusDays(7)); break; case LAST_30_DAYS: returnValue = getFileDate(file).isAfter(LocalDate.now().minusDays(30)); break; default: returnValue = true; } return returnValue; } /* * Returns file's last modified date as a LocalDate. */ private LocalDate getFileDate(Path file) throws IOException { Instant fileTime = Files.getLastModifiedTime(file).toInstant(); return fileTime.atZone(ZoneId.systemDefault()).toLocalDate(); } }
AppStarter.java
package com.javaquizplayer.examples.copyfilesapp; import javafx.application.Application; import javafx.application.Platform; import javafx.stage.Stage; import java.io.IOException; import java.util.stream.Stream; import java.util.logging.Logger; import java.util.logging.Level; import java.util.logging.Handler; /* * The copy files app's starter program. * Launches the app and the main GUI. */ public class AppStarter extends Application { private static Logger logger; private static final String LOG_FILE = "copy_files_app_log_%g.txt"; public static void main(String... args) { Application.launch(args); } /* * Does some initial configuration for the app. * Configures logger and its handlers. * Exits the application if there is an exception. */ @Override public void init() { try { configureLogging(); } catch (IOException ex) { System.out.println("An IOException occurred during app initialization."); System.out.println("This happened during the logger configuration."); System.out.println("See the stack trace below:"); ex.printStackTrace(); Platform.exit(); // NOTE: doesn't run the stop() method. } } /* * Configures the logger and registers a file handler. */ private void configureLogging() throws IOException { System.setProperty("java.util.logging.SimpleFormatter.format", "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %5$s%n"); logger = Logger.getLogger("copy_app_logger"); logger.setUseParentHandlers(false); // disables console logging logger.addHandler(new LogFileHandler(LOG_FILE)); logger.setLevel(Level.INFO); logger.info("Logger is configured"); } /* * Displays the main GUI. */ @Override public void start(Stage primaryStage) { logger.info("Launching the GUI"); new FileTreeView(primaryStage); } /* * Does cleanup and releases resources. */ @Override public void stop() { logger.info("Closing the app"); // Close the logger's file and stream handlers Stream.of(logger.getHandlers()).forEach(h -> h.close()); } }
LogFileHandler.java
package com.javaquizplayer.examples.copyfilesapp; import java.util.logging.SimpleFormatter; import java.io.IOException; /* * Handler to write log messages to a log file. */ public class LogFileHandler extends FileHandler { public LogFileHandler(String pattern) throws IOException { super(pattern); setFormatter(new SimpleFormatter()); // overrides the default xml formatter } }
TextAreaLogHandler.java
package com.javaquizplayer.examples.copyfilesapp; import java.util.logging.StreamHandler; import java.util.logging.LogRecord; import javafx.scene.control.TextArea; import javafx.application.Platform; /* * Handler to write log messages to the app's GUI (status message area, * a TextArea control, of the CopyDialog.java). */ public class TextAreaLogHandler extends StreamHandler { TextArea textArea = null; public TextAreaLogHandler(TextArea textArea) { this.textArea = textArea; } @Override public void publish(LogRecord record) { super.publish(record); flush(); Platform.runLater(() -> textArea.appendText(getFormatter().format(record))); } }
ZipFileCreater.java
package com.javaquizplayer.examples.copyfilesapp; import java.util.zip.ZipOutputStream; import java.util.zip.ZipEntry; import java.io.IOException; import java.io.FileInputStream; import java.io.FileOutputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.Path; import java.nio.file.FileVisitResult; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; /* * Class has one static method. * Creates a ZIP (compressed archive) file for the supplied directory with * files. This directory is the same as the target directory as a result of * copy action in Copy Dialog. * The ZIP file created is in the same directory in which the supplied directory * is present. */ public class ZipFileCreater { public static String zip(Path input) throws IOException { String targetFileNameStr = input.getFileName().toString() + ".zip"; Path targetPath = Paths.get(input.getParent().toString(), targetFileNameStr); ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(targetPath.toString())); Files.walkFileTree(input, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Path targetfile = input.relativize(file); ZipEntry zipEntry = new ZipEntry(targetfile.toString()); zipOutputStream.putNextEntry(zipEntry); try(FileInputStream fileInputStream = new FileInputStream(file.toString())) { byte [] buf = new byte [512]; int bytesRead; while ((bytesRead = fileInputStream.read(buf)) > 0) { zipOutputStream.write(buf, 0, bytesRead); } } zipOutputStream.closeEntry(); return FileVisitResult.CONTINUE; } }); zipOutputStream.close(); return targetPath.toString(); } }
Return to top
This page uses Java code formatting from http://hilite.me/.