I tried to write code for counting files of certain type on my computer.
I tested both one thread solution and multi-threads asynch solution, and it seems like the one thread is working faster. Is anything wrong with my code? and if not, why isn't it working faster?
The code below:
AsynchFileCounter - The asynchronized version.
ExtensionFilter - The file filter to list only directories and files with the extension specified
BasicFileCounter - The one thread version.
public class AsynchFileCounter {
public int countFiles(String path, String extension) throws InterruptedException, ExecutionException {
ExtensionFilter filter = new ExtensionFilter(extension, true);
File f = new File(path);
return countFilesRecursive(f, filter);
}
private int countFilesRecursive(File f, ExtensionFilter filter) throws InterruptedException, ExecutionException {
return CompletableFuture.supplyAsync(() -> f.listFiles(filter))
.thenApplyAsync(files -> {
int count = 0;
for (File file : files) {
if(file.isFile())
count++;
else
try {
count += countFilesRecursive(file, filter);
} catch (Exception e) {
e.printStackTrace();
}
}
return count;
}).get();
}
}
public class ExtensionFilter implements FileFilter {
private String extension;
private boolean allowDirectories;
public ExtensionFilter(String extension, boolean allowDirectories) {
if(extension.startsWith("."))
extension = extension.substring(1);
this.extension = extension;
this.allowDirectories = allowDirectories;
}
#Override
public boolean accept(File pathname) {
if(pathname.isFile() && pathname.getName().endsWith("." + extension))
return true;
if(allowDirectories) {
if(pathname.isDirectory())
return true;
}
return false;
}
}
public class BasicFileCounter {
public int countFiles(String path, String extension) {
ExtensionFilter filter = new ExtensionFilter(extension, true);
File f = new File(path);
return countFilesRecursive(f, filter);
}
private int countFilesRecursive(File f, ExtensionFilter filter) {
int count = 0;
File [] ar = f.listFiles(filter);
for (File file : ar) {
if(file.isFile())
count++;
else
count += countFilesRecursive(file, filter);
}
return count;
}
}
You have to spawn multiple asynchronous jobs and must not wait immediately for their completion:
public int countFiles(String path, String extension) {
ExtensionFilter filter = new ExtensionFilter(extension, true);
File f = new File(path);
return countFilesRecursive(f, filter).join();
}
private CompletableFuture<Integer> countFilesRecursive(File f, FileFilter filter) {
return CompletableFuture.supplyAsync(() -> f.listFiles(filter))
.thenCompose(files -> {
if(files == null) return CompletableFuture.completedFuture(0);
int count = 0;
CompletableFuture<Integer> fileCount = new CompletableFuture<>(), all=fileCount;
for (File file : files) {
if(file.isFile())
count++;
else
all = countFilesRecursive(file, filter).thenCombine(all, Integer::sum);
}
fileCount.complete(count);
return all;
});
}
Note that File.listFiles may return null.
This code will count all files of a directory immediately but launch a new asynchronous job for sub-directories. The results of the sub-directory jobs are combined via thenCombine, to sum their results. For simplification, we create another CompletableFuture, fileCount to represent the locally counted files. thenCompose returns a future which will be completed with the result of the future returned by the specified function, so the caller can use join() to wait for the final result of the entire operation.
For I/O operations, it may help to use a different thread pool, as the default ForkJoinPool is configured to utilize the CPU cores rather the I/O bandwidth:
public int countFiles(String path, String extension) {
ExecutorService es = Executors.newFixedThreadPool(30);
ExtensionFilter filter = new ExtensionFilter(extension, true);
File f = new File(path);
int count = countFilesRecursive(f, filter, es).join();
es.shutdown();
return count;
}
private CompletableFuture<Integer> countFilesRecursive(File f,FileFilter filter,Executor e){
return CompletableFuture.supplyAsync(() -> f.listFiles(filter), e)
.thenCompose(files -> {
if(files == null) return CompletableFuture.completedFuture(0);
int count = 0;
CompletableFuture<Integer> fileCount = new CompletableFuture<>(), all=fileCount;
for (File file : files) {
if(file.isFile())
count++;
else
all = countFilesRecursive(file, filter,e).thenCombine(all,Integer::sum);
}
fileCount.complete(count);
return all;
});
}
There is no best number of threads, this depends on the actual execution environment and would be subject to measuring and tuning. When the application is supposed to run in different environments, this should be a configurable parameter.
But consider that you might be using the wrong tool for the job. An alternative are Fork/Join tasks, which support interacting with the thread pool to determine the current saturation, so once all worker threads are busy, it will proceed scanning locally with an ordinary recursion rather than submitting more asynchronous jobs:
public int countFiles(String path, String extension) {
ExtensionFilter filter = new ExtensionFilter(extension, true);
File f = new File(path);
return POOL.invoke(new FileCountTask(f, filter));
}
private static final int TARGET_SURPLUS = 3, TARGET_PARALLELISM = 30;
private static final ForkJoinPool POOL = new ForkJoinPool(TARGET_PARALLELISM);
static final class FileCountTask extends RecursiveTask<Integer> {
private final File path;
private final FileFilter filter;
public FileCountTask(File file, FileFilter ff) {
this.path = file;
this.filter = ff;
}
#Override
protected Integer compute() {
return scan(path, filter);
}
private static int scan(File directory, FileFilter filter) {
File[] fileList = directory.listFiles(filter);
if(fileList == null || fileList.length == 0) return 0;
List<FileCountTask> recursiveTasks = new ArrayList<>();
int count = 0;
for(File file: fileList) {
if(file.isFile()) count++;
else {
if(getSurplusQueuedTaskCount() < TARGET_SURPLUS) {
FileCountTask task = new FileCountTask(file, filter);
recursiveTasks.add(task);
task.fork();
}
else count += scan(file, filter);
}
}
for(int ix = recursiveTasks.size() - 1; ix >= 0; ix--) {
FileCountTask task = recursiveTasks.get(ix);
if(task.tryUnfork()) task.complete(scan(task.path, task.filter));
}
for(FileCountTask task: recursiveTasks) {
count += task.join();
}
return count;
}
}
I figured it out. since I am adding up the results in this line:
count += countFilesRecursive(file, filter);
and using get() to receive the result, I am actually waiting for the result, instead of really parallelising the code.
This is my current code, which actually runs much faster than the one thread code. However, I could not figure out an elegant way of knowing when the parallel method is done.
I would love to hear how should I solve that?
Here's the ugly way I am using:
public class AsynchFileCounter {
private LongAdder count;
public int countFiles(String path, String extension) {
count = new LongAdder();
ExtensionFilter filter = new ExtensionFilter(extension, true);
File f = new File(path);
countFilesRecursive(f, filter);
// ******** The way I check whether The function is done **************** //
int prev = 0;
int cur = 0;
do {
prev = cur;
try {
Thread.sleep(50);
} catch (InterruptedException e) {}
cur = (int)count.sum();
} while(cur>prev);
// ******************************************************************** //
return count.intValue();
}
private void countFilesRecursive(File f, ExtensionFilter filter) {
CompletableFuture.supplyAsync(() -> f.listFiles(filter))
.thenAcceptAsync(files -> {
for (File file : files) {
if(file.isFile())
count.increment();
else
countFilesRecursive(file, filter);
}
});
}
}
I did some changes to the code:
I use AtomicInteger to count the files instead of the LongAdder.
After reading Holger's answer, I decided to count directories being processed. When the number goes down to zero, the work is done. So I added a lock and a condition to let the main thread know when the work is done.
I added a check whether the file.listFiles() returns a null. I ran the code on windows and it never did (I had an empty directory, and it returned an empty array), but since it is using native code, it might return null on other OS.
public class AsynchFileCounter {
private AtomicInteger count;
private AtomicInteger countDirectories;
private ReentrantLock lock;
private Condition noMoreDirectories;
public int countFiles(String path, String extension) {
count = new AtomicInteger();
countDirectories = new AtomicInteger();
lock = new ReentrantLock();
noMoreDirectories = lock.newCondition();
ExtensionFilter filter = new ExtensionFilter(extension, true);
File f = new File(path);
countFilesRecursive(f, filter);
lock.lock();
try {
noMoreDirectories.await();
} catch (InterruptedException e) {}
finally {
lock.unlock();
}
return count.intValue();
}
private void countFilesRecursive(File f, ExtensionFilter filter) {
countDirectories.getAndIncrement();
CompletableFuture.supplyAsync(() -> f.listFiles(filter))
.thenAcceptAsync(files -> countFiles(filter, files));
}
private void countFiles(ExtensionFilter filter, File[] files) {
if(files != null) {
for (File file : files) {
if(file.isFile())
count.incrementAndGet();
else
countFilesRecursive(file, filter);
}
}
int currentCount = countDirectories.decrementAndGet();
if(currentCount == 0) {
lock.lock();
try {
noMoreDirectories.signal();
}
finally {
lock.unlock();
}
}
}
}
Related
I am quite new on Stack Overflow and a beginner in Java so please forgive me if I have asked this question in an improper way.
PROBLEM
I have an assignment which tells me to make use of multi-threading to search files for a given word, which might be present in any file of type .txt and .html, on any-level in the given directory (So basically the entire directory). The absolute file path of the file has to be displayed on the console if the file contains the given word.
WHAT HAVE I TRIED
So I thought of dividing the task into 2 sections, Searching and Multithreading respectively,
I was able to get the Searching part( File_search.java ). This file has given satisfactory results by searching through the directory and finding all the files in it for the given word.
File_search.java
public class File_search{
String fin_output = "";
public String searchInTextFiles(File dir,String search_word) {
File[] a = dir.listFiles();
for(File f : a){
if(f.isDirectory()) {
searchInTextFiles(f,search_word);
}
else if(f.getName().endsWith(".txt") || f.getName().endsWith(".html") || f.getName().endsWith(".htm") ) {
try {
searchInFile(f,search_word);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
return fin_output;
}
public void searchInFile(File f,String search_word) throws FileNotFoundException {
final Scanner sc = new Scanner(f);
while(sc.hasNextLine()) {
final String lineFromFile = sc.nextLine();
if(lineFromFile.contains(search_word)) {
fin_output += "FILE : "+f.getAbsolutePath().toString()+"\n";
}
}
}
Now, I want to be able to use multiple threads to execute the task File_search.java using ThreadPoolExecuter service. I'm not sure If I can do it using Runnable ,Callable or by using a Thread class or by any other method?
Can you please help me with the code to do the multi-threading part? Thanks :)
I agree to the comment of #chrylis -cautiouslyoptimistic, but for the purpose of understanding below will help you.
One simpler approach could be to do the traversal of directories in the main Thread, I mean the logic which you have added in function searchInTextFiles and do the searching logic as you did in function searchInFile in a Threadpool of size let's say 10.
Below sample code will help you to understand it better.
public class Traverser {
private List<Future<String>> futureList = new ArrayList<Future<String>>();
private ExecutorService executorService;
public Traverser() {
executorService = Executors.newFixedThreadPool(10);
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
System.out.println("Started");
long start = System.currentTimeMillis();
Traverser traverser = new Traverser();
traverser.searchInTextFiles(new File("Some Directory Path"), "Some Text");
for (Future<String> future : traverser.futureList) {
System.out.println(future.get());
}
traverser.executorService.shutdown();
while(!traverser.executorService.isTerminated()) {
System.out.println("Not terminated yet, sleeping");
Thread.sleep(1000);
}
long end = System.currentTimeMillis();
System.out.println("Time taken :" + (end - start));
}
public void searchInTextFiles(File dir,String searchWord) {
File[] filesList = dir.listFiles();
for(File file : filesList){
if(file.isDirectory()) {
searchInTextFiles(file,searchWord);
}
else if(file.getName().endsWith(".txt") || file.getName().endsWith(".html") || file.getName().endsWith(".htm") ) {
try {
futureList.add(executorService.submit(new SearcherTask(file,searchWord)));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}}
public class SearcherTask implements Callable<String> {
private File inputFile;
private String searchWord;
public SearcherTask(File inputFile, String searchWord) {
this.inputFile = inputFile;
this.searchWord = searchWord;
}
#Override
public String call() throws Exception {
StringBuilder result = new StringBuilder();
Scanner sc = null;
try {
sc = new Scanner(inputFile);
while (sc.hasNextLine()) {
final String lineFromFile = sc.nextLine();
if (lineFromFile.contains(searchWord)) {
result.append("FILE : " + inputFile.getAbsolutePath().toString() + "\n");
}
}
} catch (Exception e) {
//log error
throw e;
} finally {
sc.close();
}
return result.toString();
}}
Below is the piece of code which I have tried. Each file is having an integer and I want to add all the integer and display the output
#Override
public void run() {
BlockingQueue<Integer> d;
try {
d = readFile(file);
//System.out.println("adding the integers ..."+d.take());
i = (int) d.take();
System.out.println("i = "+i);
sum = sum + i;
//System.out.println("ai = "+ai.incrementAndGet());
System.out.println("sum = "+sum );
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
// ProcessedData p = d.process();
// writeFile(file.getAbsolutePath(), "C:/test");
}
private BlockingQueue<Integer> readFile(File file2) throws IOException, InterruptedException {
FileReader fr = new FileReader(file2);
BufferedReader in = new BufferedReader(new java.io.FileReader(file2));
int content = Integer.parseInt(in.readLine());
System.out.println("content = "+content);
System.out.println("reading and writing to blocking queue...");
blockingQueue.put(content);
return blockingQueue;
}
Here is the solution for the problem -
When I am adding all the integers from the queue using atomic integer, each thread has a different copy of the atomic variable.
So, Every time when addAndGet method is used it is updated with the value from the blocking queue.
I have seggregated and created a singleton class which returns me the same atomic integer object for every thread when requested.
Below is the snippet of code and this solved my problem -
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicState {
private static final AtomicState as = new AtomicState();
public AtomicInteger ai = new AtomicInteger();
private AtomicState() {
}
public AtomicInteger getAtomicIntegerObj() {
return ai;
}
public static AtomicState getAtomicState() {
return as;
}
}
I'm using Karate for testing REST API, now I'm trying to run feature files in parallel:
#CucumberOptions(tags = { "#someTest" })
public class ParallelTest {
#Test
public void testParallel() {
KarateStats stats = CucumberRunner.parallel(getClass(), 5,
"target/surefire-reports/cucumber-html-reports");
Assert.assertTrue(stats.getFailCount() == 0, "scenarios failed");
}
}
The test runs only 3 feature files in parallel and doesn't run all 5 features.
I got this code from CucumberRunner.parallel function:
CucumberRunner runner = new CucumberRunner(this.getClass());
List<FeatureFile> featureFiles = runner.getFeatureFiles();
Then tried to load my feature files, the list size is 3, that means the function didn't load all features.
Any idea why this is happening?
Note: all feature files under the same package.
Parallel() function code:
public static KarateStats parallel(Class clazz, int threadCount, String reportDir) {
KarateStats stats = KarateStats.startTimer();
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
CucumberRunner runner = new CucumberRunner(clazz);
List<FeatureFile> featureFiles = runner.getFeatureFiles();
List<Callable<KarateJunitFormatter>> callables = new ArrayList<>(featureFiles.size());
int count = featureFiles.size();
for (int i = 0; i < count; i++) {
int index = i + 1;
FeatureFile featureFile = featureFiles.get(i);
callables.add(() -> {
String threadName = Thread.currentThread().getName();
KarateJunitFormatter formatter = getFormatter(reportDir, featureFile);
logger.info(">>>> feature {} of {} on thread {}: {}", index, count, threadName, featureFile.feature.getPath());
runner.run(featureFile, formatter);
logger.info("<<<< feature {} of {} on thread {}: {}", index, count, threadName, featureFile.feature.getPath());
formatter.done();
return formatter;
});
}
try {
List<Future<KarateJunitFormatter>> futures = executor.invokeAll(callables);
stats.stopTimer();
for (Future<KarateJunitFormatter> future : futures) {
KarateJunitFormatter formatter = future.get();
stats.addToTestCount(formatter.getTestCount());
stats.addToFailCount(formatter.getFailCount());
stats.addToSkipCount(formatter.getSkipCount());
stats.addToTimeTaken(formatter.getTimeTaken());
if (formatter.isFail()) {
stats.addToFailedList(formatter.getFeaturePath());
}
}
stats.printStats(threadCount);
return stats;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Thanks :)
The simplest explanation is that the tags in the #CucumberOptions is having an effect. Try commenting it out and try again. Else there is nothing I can make out from the information you have provided.
I'm new to Java concurrent API and I've searched but didn't find an answer to my question.
Well, I have a code that look for every file inside directories and their subdirectories and another that copy every found file that match a specified pattern.
I separate this codes in one Runnable implementation called DirSearch and one Callable implementation called FileSearch and submit them using an ExecutorService.
That's the code:
private boolean execute() {
ExecutorService executor = Executors.newFixedThreadPool(threadsNumber);
BlockingQueue<File> dirQueue = new LinkedBlockingQueue<>();
BlockingQueue<File> fileQueue = new LinkedBlockingQueue<>(10000);
boolean isFinished = false;
try {
for(int i = 0; i < dirThreads; i++) {
executor.submit(new DirSearch(dirQueue, fileQueue, count, dirThreads);
}
count.incrementAndGet();
dirQueue.add(baseDir);
Future<Boolean> future = executor.submit(new FileSearch(filequeue, outputDirectory, filename));
isFinished = future.get();
} catch(ExecutionException | InterruptedException | RuntimeException ex) {
ex.printStackTrace();
} finally {
executor.shutdownNow();
}
return isFinished;
}
...
private void copyFile(File in, File out) {
Path inPath = Paths.get(in.getAbsolutePath());
Path outPath = Paths.get(out.getAbsolutePath(), in.getName());
try {
main.updateCurrentLabel(outPath.toString());
switch(mode) {
case "1":
Files.copy(inPath, outPath, StandardCopyOption.REPLACE_EXISTING);
break;
case "2":
Files.move(inPath, outPath, StandardCopyOption.REPLACE_EXISTING);
break;
default:
break;
}
main.updateCopiedLabel(String.valueOf(countCpFiles.incrementAndGet()));
} catch(IOException ex) {
ex.printStackTrace();
}
}
...
private class DirSearch implements Runnable {
...
#Override
public void run() {
try {
File dir = dirQueue.take();
while(dir != new File("")) {
File[] elements = dir.listFiles();
if(elements != null) {
for(File element : elements) {
if(element.isDirectory()) {
count.incrementAndGet();
dirQueue.put(element);
} else {
fileQueue.put(element);
}
}
}
if(count.decrementAndGet() == 0) {
end();
}
dir = dirQueue.take();
}
} catch(InterruptedException ex) {
ex.printStackTrace();
}
}
...
}
...
private class FileSearch implements Callable<Boolean> {
...
#Override
public Boolean call() {
boolean isFinished = false;
try {
File file = fileQueue.take();
while(file != new File("")) {
incrementAnalyzed();
String foundFile = file.getName().toLowerCase();
if(foundFile.matches(filename.replace("?", ".?").replace("*", ".*?"))) {
copyFile(file, outputDirectory);
}
file = fileQueue.take();
}
isFinished = true;
} catch(InterruptedException ex) {
ex.printStackTrace();
}
return isFinished;
}
}
The problem is: when the FileSearch start to copy files, the other threads (DirSearch) stop and don't look for any new file until the copy is completed. Why this is happening? Am I doing anything wrong or this is not the correct approach?
Two possible answers which came to my mind and which i cant guarantee they apply to your specific situation:
1. Java VM gets only one core from your CPU which means it can only run one thread at a time.
2. your threads both use the same variable which means only one is allowed to really manipulate it at a time. For this specific problem look up java keyword "synchronized".
I guess the root of the problem tends to be #1
I need create a new constructor in FolderScan that takes a list of "Checkers". And all these "Checkers" always return true (schoud write new Chekers List that just return true.)
But problem is that I don't know how do this and not decompose structure of program.
Code (FolderScan and each Cheker):
class FolderScan implements Runnable {
FolderScan(String path, BlockingQueue<File> queue, CountDownLatch latch,
File endOfWorkFile) {
this.path = path;
this.queue = queue;
this.latch = latch;
this.endOfWorkFile = endOfWorkFile;
checkers = new ArrayList<Checker>(Arrays.asList(
new ExtentionChecking(), new ProbeContentTypeCheking(),
new EncodingChecking() ));
}
#Override
public void run() {
try {
findFiles(path);
queue.put(endOfWorkFile);
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void findFiles(String path) {
try {
File root = new File(path);
File[] list = root.listFiles();
for (File currentFile : list) {
boolean checksPassed = true;
if (currentFile.isDirectory()) {
findFiles(currentFile.getAbsolutePath());
} else {
for (Checker currentChecker : checkers) {
if (!currentChecker.check(currentFile)) {
checksPassed = false;
break;
}
}
if (checksPassed) {
queue.put(currentFile);
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
private String path;
private BlockingQueue<File> queue;
private CountDownLatch latch;
private File endOfWorkFile;
private List<Checker> checkers;
}
class ExtentionChecking implements Checker {
#Override
public boolean check(File currentFile) {
fileName = currentFile.getName().toLowerCase();
Set<String> extensions = new HashSet<String>(Arrays.asList(".txt",
".pdf", ".doc", ".docx", ".html", ".htm", ".xml", ".djvu",
".djv", ".rar", ".rtf", ".tmp"));
if (extensions.contains(fileName.substring(fileName.lastIndexOf(".")))) {
return true;
}
return false;
}
private String fileName;
}
class EncodingChecking implements Checker {
#Override
public boolean check(File currentFile) {
return detectEncoding(currentFile);
}
public static boolean detectEncoding(File file) {
detector = new CharsetDetector();
// validate input
if (null == file) {
throw new IllegalArgumentException("input file can't be null");
}
if (file.isDirectory()) {
throw new IllegalArgumentException(
"input file refers to a directory");
}
// read input file
byte[] buffer;
try {
buffer = readUTFHeaderBytes(file);
} catch (IOException e) {
throw new IllegalArgumentException(
"Can't read input file, error = " + e.getLocalizedMessage());
}
if(detector.setText(buffer) != null){
return true;
}
return false;
}
private static byte[] readUTFHeaderBytes(File input) throws IOException {
// read data
FileInputStream fileInputStream = new FileInputStream(input);
try {
byte firstBytes[] = new byte[50];
int count = fileInputStream.read(firstBytes);
if (count < 5) {
throw new IOException("Poor file!");
}
return firstBytes;
} finally {
fileInputStream.close();
}
}
private static CharsetDetector detector;
}
class ProbeContentTypeCheking implements Checker {
#Override
public boolean check(File currentFile) {
String mimeType = null;
try {
Path path = Paths.get(currentFile.getAbsolutePath());
byte[] data = Files.readAllBytes(path);
MagicMatch match = Magic.getMagicMatch(data);
mimeType = match.getMimeType();
} catch (MagicParseException | MagicMatchNotFoundException
| MagicException | IOException e) {
e.printStackTrace();
}
if (null != mimeType) {
return true;
}
return false;
}
}
Question:
How do refactor this code - after this able to make new
AllwaysPassesBlocker() and all Checers return true?
A checker that always returns true would be
class UncriticalChecker implements Checker {
#Override
public boolean check(File currentFile) {
return true;
}
}
There's no point adding such a checker to the list of checkers, though. You might as well leave the list empty.
I don't quite see why the checkers should be constructed in the constructor of the FolderScan. It seems more natural to pass them to the constructor as an argument.
FolderScan(String path, BlockingQueue<File> queue, CountDownLatch latch,
File endOfWorkFile, List<Checker> checkers) {
this.path = path;
this.queue = queue;
this.latch = latch;
this.endOfWorkFile = endOfWorkFile;
this.checkers = checkers;
}
Then, when you initialize the FolderScan, pass it the checkers
List<Checker> checkers = new ArrayList<Checker>(Arrays.asList(
new ExtentionChecking(), new ProbeContentTypeCheking(),
new EncodingChecking() ));
FolderScan folderScan =
new FolderScan(path, queue, latch, endOfWorkFile, checkers);
Or, if you wish to create a FolderScan that returns all files, you pass it an empty list.
FolderScan folderScan =
new FolderScan(path, queue, latch, endOfWorkFile, Collections.emptyList());
EDIT:
I now understand that you wish to test the class. Then the UncriticalChecker makes sense. If you want to test the code with a checker that always says yes, pass it to the constructor:
List<Checker> checkers = Collections.singletonList(new UncriticalChecker());
FolderScan folderScan =
new FolderScan(path, queue, latch, endOfWorkFile, checkers);