/* 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) v5_i1 (version 5, iteration/intermediate 1) 19Jan2020: Made the search case-insensitive, as this is generally more useful to me. (However might add a choice between case sensitive and insensitive in the future.) Items that might be added/addressed later: --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)? Maybe investigate use of SwingWorker */ 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_v5_i1 { 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_v5_i1() // 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(); 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 { Stream targetStream = Files.find(validatedStartPath, walkDepth, (p,a) -> !isHiddenHandler(p) && // to exclude hidden (temporary etc) files // v5_i1 alteration // toStringHandled(p.getFileName().contains(target)); toStringHandled(p.getFileName()).toLowerCase().contains(target.toLowerCase())); // (toLowerCase() call makes it case-insensitive; could remove again, or make optional targetStream.forEach(p -> displayTextArea.append(toStringHandled(p) + "\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.setWrapStyleWord(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; } String toStringHandled (Path p) // as Path's toString() can throw NullPointerException... { // ...easier to handle it externally than in stream coded in findButton's addActionListener above String result = ""; try { result = p.toString(); // p is filename for start path, and is null if that is root, e.g. C:\ } catch (NullPointerException npEx) // ...in which case NullPointerException is thrown { displayTextArea.setText("Note: as you are starting from the root, " + "the search may terminate early if subfolders without access permission are encountered. " + "(This is a known issue, and may also affect searches starting further down the hierarchy, " + "in which case you will not see feedback, unfortunately)." + " May be addressed in a subsequent version of the program." + "\n\n"); } return result; } public static void main(String[] args) { new FindFileOrFolder_v5_i1(); } }