Reputation: 20090
Given a list of directory names, I would like to produce a mapping of all files in the directories to their contents.
Any suggestions for a more explicit implementation?
Map<File, String> map = new HashMap<>();
Arrays.stream(filenames)
.map(s -> FileUtils.listFiles(new File(rootdir, s), new RegexFileFilter(".*"), DirectoryFileFilter.DIRECTORY))
.flatMap(Collection::stream)
.forEach(f -> {
try {
map.put(f, FileUtils.readFileToString(f));
}
catch (IOException e) {
e.printStackTrace();
}
});
return map;
`
Upvotes: 3
Views: 103
Reputation: 131326
Your way of doing is really not bad but I wonder whether in your case using streams is the best way to get a more explicit code.
The Aaron solution is a little bit straighter and more "stream way" as it collects the map as terminal operation but it also changes the original logic in case of thrown IOException
.
A version without stream appears really straight :
Map<File, String> map = new HashMap<>();
for (String filename : filenames) {
for (File file : FileUtils.listFiles(new File(rootdir, filename), new RegexFileFilter(".*"), DirectoryFileFilter.DIRECTORY)) {
try {
map.put(file, FileUtils.readFileToString(file));
} catch (IOException e) {
// log exception
}
}
}
Edit : the solution of tsolakp with stream is really fine even if it stays less straight than the way using classic loops.
Here is a lighter version.
I think that using Optional
internally to the stream is not required. It is "our" stream, so we master what we put in (here null
for failure case).
In addition, including the code in the map()
processing may seem less "functional" but it stays straight readable and I find it preferable to reading indirection.
import java.util.AbstractMap.SimpleImmutableEntry;
// ...
Map<File, String> map = Arrays.stream(filenames)
.flatMap( s -> FileUtils.listFiles( new File(rootdir, s), new RegexFileFilter(".*"), DirectoryFileFilter.DIRECTORY ).stream() )
.map( f -> {
try{
return new SimpleImmutableEntry<>(f, FileUtils.readFileToString(f) );
}catch(IOException e){
//log exception
return null;
}
}
)
.filter(Objects::nonNull)
.collect(Collectors.toMap(SimpleImmutableEntry::getKey, SimpleImmutableEntry::getValue));
Upvotes: 3
Reputation: 24802
You should use Collectors.toMap
:
Map<File, String> map = Arrays.stream(filenames)
.map(s -> FileUtils.listFiles(new File(rootdir, s), new RegexFileFilter(".*"), DirectoryFileFilter.DIRECTORY))
.flatMap(Collection::stream)
.collect(Collectors.toMap(
Function.identity(),
f-> {
try { return FileUtils.readFileToString(f); }
catch (IOException ioe) { return null; }
}
));
Its first parameter is the key mapping function. The unmodified files should be used as keys, so we can use the identity function.
Its second parameter is the value mapping function, where we map the File
s to their content.
Note that my value mapping function will produce null
values and won't output any error when a file's content can't be read. Consider using one of the other answers unless you're confident such IOException
will never happen.
Upvotes: 3
Reputation: 5948
"more explicit implementation" is really opinion based. For me using plain loop like @davidxxx did is the preferred way. You can of course use Java streams too but their lack of handling exceptions will make your code with try/catch statement ugly. One option is to move the try/catch into separate method or utility class and keep the stream expression cleaner with something like this:
Map<File, String> map = Arrays.stream(filenames)
.flatMap( s -> FileUtils.listFiles( new File(rootdir, s), new RegexFileFilter(".*"), DirectoryFileFilter.DIRECTORY ).stream() )
.map( f -> new AbstractMap.SimpleImmutableEntry<>(f, read(f) ) )
.filter( e -> e.getValue().isPresent() )
.collect( Collectors.toMap( e -> e.getKey(), e -> e.getValue().get() ) );
private Optional<String> read(File f){
try{
return Optional.of( FileUtils.readFileToString(f) );
}catch(IOException e){
//log exception
return Optional.empty();
}
}
Upvotes: 1