0

I would like to search for files recursively. According to other solutions, I have already done a big portion of the code:

public static File[] getFiles(String path) {
    File file = new File(path);

    // Get the subdirectories.
    String[] directories = file.list(new FilenameFilter() {
       @Override
       public boolean accept(File current, String name) {
         return new File(current, name).isDirectory();
       }
    });
    for (String dir : directories) {
        // Doing recursion
    }

    // Get the files inside the directory.
    FileFilter fileFilter = new FileFilter();  
    File[] files = file.listFiles(fileFilter);

    return files;
}

FileFilter is just a custom filter of mine. My problem is that I don't know how to do the recursion in this case. Of course I could call getFiles() again for each subdirectory with the subdirectory path as argument but somehow the returning File array must be merged.

Does somebody have a solution?

machinery
  • 5,972
  • 12
  • 67
  • 118
  • Are you aware all of this is provided for you in `java.nio.file.Files.walkFileTree()`? – Jim Garrison Feb 11 '16 at 23:42
  • How would you do it with walkFileTree so that I still can use my custom file filter? My file filter filters files for specific extension and for specific name (before file extension). – machinery Feb 11 '16 at 23:47
  • In that case, the built-in glob support would work for you. – erickson Feb 12 '16 at 00:33
  • If it's any help, I am building a small app that uses something *like* this [here](https://github.com/Majora320/FileRenamer/). – Majora320 Feb 12 '16 at 01:59
  • You can use a lambda expression for implementing functional interfaces. It's much less wordy and makes you look like a pro. `String[] directories = file.list((current, name) -> new File(current, name).isDirectory());` – 4castle Feb 12 '16 at 02:51

4 Answers4

2

Use the find() method.

/* Your filter can be initialized however you need... */
YourCustomFilter filter = new YourCustomFilter(extension, maxSize);
try (Stream<Path> s = Files.find(dir, Integer.MAX_VALUE, filter::test)) {
  return s.map(Path::toFile).toArray(File[]::new);
}

This assumes your custom filter has a method called test() that accepts the file and its attributes; you'll need to rework your current file filter a bit to accommodate this.

boolean test(Path path, BasicFileAttributes attrs) {
  ...
}
erickson
  • 265,237
  • 58
  • 395
  • 493
  • The `::` operator is new to me in Java. What does it do? – 4castle Feb 12 '16 at 00:43
  • @4castle It refers to a method. So instead of writing a lambda like `(f, a) -> filter.test(f, a)`, you can just reference the method directly, `filter::test`. When you can do this, the compiler doesn't need to create an anonymous inner class for your lambda. – erickson Feb 12 '16 at 01:20
  • Instead of `System.out::println` couldn't it do something more applicable to the question like `files::add`? Then `return files.toArray(new File[0]);` – 4castle Feb 12 '16 at 03:16
  • @4castle Yes, but it's more straightforward than that. There's a utility method on `Stream` to collect into an array directly. See my edit. If you want a `List` or other collection, you'd use the `collect()` method with a suitable `Collector`. – erickson Feb 12 '16 at 04:33
  • Perfect! I've never seen a single colon used that way before to call new. How does that work? Is it just shorthand for an empty array/default constructor? – 4castle Feb 12 '16 at 14:33
  • @4castle Oops, that is a typo, it's supposed to be `::`, like the others. In the context of a method reference `ClassType::new` means call a constructor on that class. And in the case of an array constructor, there's an additional special behavior that accepts a single `int` as the length of a 1-dimensional array. You can read more [here.](https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13) – erickson Feb 12 '16 at 16:45
1

Working example: http://screencast.com/t/buiyV9UiEa

You can try something like this:

//add this imports    
import java.io.File;
import java.io.FilenameFilter;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;

public static File[] getFiles(String path) {
        File file = new File(path);

    // Get the subdirectories.
    String[] directories = file.list(new FilenameFilter() {
       @Override
       public boolean accept(File current, String name) {
         return new File(current, name).isDirectory();
       }
    });

    //Use a list to save the files returned from the recursive call
    List<File> filesList = new ArrayList<File>();

    if( directories != null){
        for (String dir : directories) {
            // Doing recursion
            filesList.addAll( Arrays.asList(getFiles(path + File.separator + dir)) );
        }
    }
    // Get the files inside the directory.
    FileFilter fileFilter = new FileFilter();  
    File[] files = file.listFiles(fileFilter);

    //Merge the rest of the files with the files
    //in the current dir
    if( files != null)
        filesList.addAll( Arrays.asList(files) );


    return filesList.toArray(new File[filesList.size()]);
}

Code tested and working. Hope this helps.

kevinkl3
  • 961
  • 7
  • 22
  • It does not work for me, it does not find any files. :( In the path I have 4 subfolders and inside each such subfolder there are files. – machinery Feb 12 '16 at 00:51
  • @machinery I've updated the code and also added an example, the argument passed in the recursive call was wrong, changed to `path + File.separator + dir`. – kevinkl3 Feb 12 '16 at 04:30
1
import java.util.Arrays;
import java.util.ArrayList;

Put a fail-safe right after you initialize file (in case of a bad path on the first call).

if (!file.isDirectory()) return new File[0];

And change the last part of your code to:

FileFilter fileFilter = new FileFilter();
ArrayList<File> files = new ArrayList(Arrays.asList(file.listFiles(fileFilter)));

for (String dir : directories) {
    files.addAll(Arrays.asList(getFiles(dir)));
}

return files.toArray(new File[0]);

(the toArray method expands the array that you pass to it if it's too small) Ref

Community
  • 1
  • 1
4castle
  • 32,613
  • 11
  • 69
  • 106
0

You should do something like this:

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;

public static File[] getFiles(String path) {
    File file = new File(path);

    String[] directories = file.list(new FilenameFilter() {
       @Override
       public boolean accept(File current, String name) {
         return new File(current, name).isDirectory();
       }
    });

    ArrayList<File> files = Arrays.asList(file.listFiles(new FileFilter()));

    for (String dir : directories) {
        files.addAll(getFiles(dir));
    }

    return files.toArray(new File[list.size()]);
}

The new File[list.size()] is required because otherwise file.toArray() would return Object[].
Also, you should use a lambda expression instead of FilenameFilter, like so:

  String[] directories = file.list((File current, String name) -> {
      return new File(current, name).isDirectory();
  });
Majora320
  • 1,321
  • 1
  • 13
  • 33
  • `File.lisfFiles` returns a File array (`File[]`) not an `ArrayList` instance. – kevinkl3 Feb 11 '16 at 23:58
  • Yes, I'm getting a type mismatch. – machinery Feb 11 '16 at 23:58
  • Noted. I'm fixing it now; you have to use `Arrays.asList`. – Majora320 Feb 12 '16 at 00:02
  • `files.append()` doesn't exist. Use `files.addAll()` Also, you can just pass an array of length 0 to the `toArray` method because it will just expand the array as needed anyway before it returns. – 4castle Feb 12 '16 at 00:22
  • The `addAll` method takes a `List` as a parameter, not an array, so you have to wrap the `getFiles` method like `Arrays.asList(getFiles(dir))` – 4castle Feb 12 '16 at 02:18
  • Ok, but by this point another answer has already been accepted, so there's no point in updating this. – Majora320 Feb 12 '16 at 23:52