/* Objective: Since Windows 10 File Explorer search seems messed-up on my laptop (and computers of at least some others reporting online) since late 2019, try to make something for finding files/folders on my laptop while waiting for a fix! First just look at file/folder names (might include option to match text within files later) v3 12Jan2020 Done in this iteration/version: Dealt with a bug found by chance... --Problem: Found that some invalid entries for start path, e.g. "For C:\" (which I entered accidentally), did not trigger my "No valid path supplied" message, and any previous output stayed in the display area. (However, though the user does not receive feedback, the program did not crash and responded normally to a 'regular' subsequent search.) --Cause: InvalidPathException thrown from Paths.get(startText) --Solution: Handled the exception to divert to the "No valid path supplied" display as in the 2 set-off sections of code marked by comments beginning with "--v3" Next, might address: --Possible to allow a search to be aborted (while displaying items found up to that point) without closing the program window (& have the button text toggle to a message indicating this option)? */ import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; import java.awt.Insets; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.stream.Stream; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingUtilities; class FindFileOrFolder_v3 { String missingStartMessage = ""; String missingTargetMessage = ""; int walkDepth = 1; // --specifies how many subdirectory levels to go down when collecting files to access // initialized to default 1 (do not look in subdirectories) // GUI components... JLabel startFolderJLabel = new JLabel(" Enter the path of the folder from within which you want to (start your) search..."); JTextArea startFolderTextArea = new JTextArea(2, 60); // input to specify foolder within which to (start) searching JPanel panelForStartComponents = new JPanel(new BorderLayout()); // to hold the above pair of components JButton depthButton = new JButton("Search in subfolders of the starting folder also"); JLabel targetNameJLabel = new JLabel(" Enter the name or partial name of a file or folder you want to find..."); JTextArea targetNameTextArea = new JTextArea(2, 60); // input to specify file/folder names for which to search JPanel panelForTargetComponents = new JPanel(new BorderLayout()); // to hold the above pair of components JPanel panelForInputs = new JPanel(new BorderLayout()); // to hold panelForStartComponents, depthButton, panelForTargetComponents JButton findButton = new JButton("Find files/folders"); JTextArea displayTextArea = new JTextArea(40, 100); // displays output, i.e. paths for files/folders found JPanel panelForFindButtonAndOutput = new JPanel(new BorderLayout()); // to hold the above pair of components JFrame frame = new JFrame("FindFileOrFolder"); // to hold all above (sub)panels FindFileOrFolder_v3() // constructor, called when main method runs { panelForStartComponents.add(startFolderJLabel, BorderLayout.NORTH); startFolderTextArea.setLineWrap(true); startFolderTextArea.setMargin(new Insets(5, 5, 5, 5)); panelForStartComponents.add(new JScrollPane(startFolderTextArea), BorderLayout.SOUTH); panelForInputs.add(panelForStartComponents, BorderLayout.NORTH); panelForTargetComponents.add(targetNameJLabel, BorderLayout.NORTH); targetNameTextArea.setLineWrap(true); targetNameTextArea.setMargin(new Insets(5, 5, 5, 5)); panelForTargetComponents.add(new JScrollPane(targetNameTextArea), BorderLayout.SOUTH); panelForInputs.add(panelForTargetComponents, BorderLayout.SOUTH); findButton.addActionListener(actionEvent -> { // (the 'actionPerformed' method body of the implicit ActionListner...) String startText = startFolderTextArea.getText().trim(); // --v3 alterations made to handle possibility that InvalidPathException is thrown... Path startPath = null; // path to folder within which to (start) searching boolean startPathValid = true; try { startPath = Paths.get(startText); // ...for some start inputs on this call } catch (Exception e) // to handle possible InvalidPathException { startPathValid = false; } Path validatedStartPath = startPath; // (need an effectively final variable for use later in a lambda) if (startPathValid) // even if real path input, want to check that it a folder (cf a file), so reassign to... { startPathValid = Files.isDirectory(startPath); // (as empty string arg seems to generate Path regarded // as valid (root/current folder?), so for now including check re startText.isEmpty() below) } final String target = targetNameTextArea.getText(); // (partial) names of files/folders for which to search if (startText.isEmpty() || !startPathValid) { missingStartMessage = "No valid start path supplied" + "\n"; } if (target.isEmpty()) { missingTargetMessage = "No search term supplied" + "\n"; } if (!startText.isEmpty() && startPathValid && !target.isEmpty()) { // only run process below if target text and valid start path have been supplied (avoid wasteful processing) // first, clear any previous message or search results and display a message to say the search is in progress displayTextArea.setForeground(Color.GREEN); displayTextArea.setFont(new Font("SERIF", Font.BOLD, 20)); displayTextArea.setText("Search in progress..."); SwingUtilities.invokeLater(() -> // (want the above progress' message to appear first if search takes a while) { displayTextArea.setForeground(Color.BLACK); displayTextArea.setFont(null);// this seems to work to restore the font to default (as desired) displayTextArea.setText(null); // clear any previous text before displaying the results try { // --v3 alteration: first arg changed from startPath due to changes above Stream targetStream = Files.find(validatedStartPath, walkDepth, (p,a) -> !isHiddenHandler(p) && // to exclude hidden (temporary etc) files p.getFileName().toString().contains(target)); targetStream.forEach(p -> displayTextArea.append(p.toString() + "\n")); // print paths found in output/display area if (displayTextArea.getText().equals("")) { displayTextArea.setForeground(Color.BLUE); displayTextArea.setText("No results found"); } // (seems inelegant, but have not thought of a way to do directly from the stream code yet) } catch (IOException ex) { if (ex.getClass().getName().equals("java.nio.file.NoSuchFileException")) { missingInputsMessage(); } // (probably not needed now, though, as should not get to try clause without valid start path) } } ); } else { missingInputsMessage(); } } ); findButton.setMargin(new Insets(10, 10, 10, 10)); findButton.setFont(new Font("SansSerif", Font.BOLD, 20)); panelForFindButtonAndOutput.add(findButton, BorderLayout.NORTH); depthButton.addActionListener(actionEvent -> { if (walkDepth == 1) { walkDepth = Integer.MAX_VALUE; depthButton.setText("Search in the starting folder only"); } else // (walkDepth is Integer.MAX_VALUE) { walkDepth = 1; depthButton.setText("Search in subfolders of the starting folder also"); } } ); // toggles walk dept between no-subfolders and all-subfolders panelForInputs.add(depthButton, BorderLayout.CENTER); displayTextArea.setEditable(false); displayTextArea.setLineWrap(true); displayTextArea.setMargin(new Insets(5, 5, 5, 5)); panelForFindButtonAndOutput.add(new JScrollPane(displayTextArea), BorderLayout.SOUTH); frame.add(panelForInputs, BorderLayout.NORTH); frame.add(panelForFindButtonAndOutput, BorderLayout.SOUTH); frame.setResizable(false); // (buttons disappear if user drags frame bottom up, // while if it's dragged down, the extra space just appears as a gap between the panels, // so just hard-coding big displayTextArea for the moment; // looking briefly online, see descriptions/code for how to make rezizable by dragging, but not trivial frame.setVisible(true); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); // (may need to keep this positioned last) } void missingInputsMessage() // puts message in display area if one or both user inputs missing/incorrect { displayTextArea.setForeground(Color.red); displayTextArea.setText(missingStartMessage + missingTargetMessage); missingStartMessage = ""; // reset for future clicks missingTargetMessage = ""; // (ditto) } boolean isHiddenHandler (Path p) // as Files.isHidden(...) throws checked exception... { // ...easier to handle it externally than in stream coded in findButton's addActionListener above boolean result = false; try { result = Files.isHidden(p); } catch (IOException ex) { System.out.println(ex); } return result; } public static void main(String[] args) { new FindFileOrFolder_v3(); } }