/* 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) v4 15Jan2020: Partially addressed the following known issue: --User experience: If search start path entered is the root, e.g. "C:\", there is no output or message. (However, though the user does not receive feedback, the program does not crash and will respond normally to a 'regular' subsequent search.) --Cause: NullPointerException thrown by toString() call on start path's filename (null for root). --Improvement (though not fix) in this version: Handled the exception so that files/folders which can be accessed are displayed. However, due to annother (unrelated) known issue, that Files.find(...), as used here, throws an exception when it encounters any file/folder which to which access is not allowed, and these items (on my system, at least) are often present near the root, searching from the root is likely to terminate prematurely; I added a note about that to the displayed message. Also added a call to wrap displayTextArea contents by word (as saw the long message was broken within a word for current area width) 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_v4 { 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_v4() // 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 // --v4 alterations (using method added below) to allow handling of NullPointerException thrown // from original toString() call on null returned from p.getFileName() for input of C:\ (root) as start lcation // p.getFileName().toString().contains(target)); toStringHandled(p.getFileName()).contains(target)); // targetStream.forEach(p -> displayTextArea.append(p.toString() + "\n")); // print paths found in output/display area 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); // v4 added... 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; } // --v4 - added method 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 { System.out.println("NullPointerException has been thrown by toString() call on start path's filename (null for root)"); // might remove----------------------------------------------------------------------------------------------------------- // would like message in red but if use // displayTextArea.setForeground(Color.RED); // and then try to reset with // displayTextArea.setForeground(Color.BLACK); // for any following search results, just see all in black // apparently would need to use a JTextPane to have different rows text in different colours? // or some other arrangement...decided not to investigate further for the moment 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_v4(); } }