/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.io.filefilter;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOCase;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.file.PathUtils;
import org.apache.commons.io.file.TempFile;
import org.apache.commons.io.test.TestUtils;
import org.apache.commons.lang3.SystemUtils;
import org.junit.jupiter.api.Test;

/**
 * Tests {@link FileFilterUtils}.
 */
class FileFilterTest extends AbstractFilterTest {

    @Test
    void testAgeFilter() throws Exception {
        final File oldFile = new File(temporaryFolder, "old.txt");
        final Path oldPath = oldFile.toPath();
        final File reference = new File(temporaryFolder, "reference.txt");
        final File newFile = new File(temporaryFolder, "new.txt");
        final Path newPath = newFile.toPath();

        if (!oldFile.getParentFile().exists()) {
            fail("Cannot create file " + oldFile + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output1 = new BufferedOutputStream(Files.newOutputStream(oldFile.toPath()))) {
            TestUtils.generateTestData(output1, 0);
        }

        do {
            try {
                TestUtils.sleep(1000);
            } catch (final InterruptedException ie) {
                // ignore
            }
            if (!reference.getParentFile().exists()) {
                fail("Cannot create file " + reference + " as the parent directory does not exist");
            }
            try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(reference.toPath()))) {
                TestUtils.generateTestData(output, 0);
            }
        } while (equalsLastModified(oldFile, reference));

        final Date date = new Date();
        final long now = date.getTime();

        do {
            try {
                TestUtils.sleep(1000);
            } catch (final InterruptedException ie) {
                // ignore
            }
            if (!newFile.getParentFile().exists()) {
                fail("Cannot create file " + newFile + " as the parent directory does not exist");
            }
            try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(newFile.toPath()))) {
                TestUtils.generateTestData(output, 0);
            }
        } while (equalsLastModified(reference, newFile));

        final IOFileFilter filter1 = FileFilterUtils.ageFileFilter(now);
        final IOFileFilter filter2 = FileFilterUtils.ageFileFilter(now, true);
        final IOFileFilter filter3 = FileFilterUtils.ageFileFilter(now, false);
        final IOFileFilter filter4 = FileFilterUtils.ageFileFilter(date);
        final IOFileFilter filter5 = FileFilterUtils.ageFileFilter(date, true);
        final IOFileFilter filter6 = FileFilterUtils.ageFileFilter(date, false);
        final IOFileFilter filter7 = FileFilterUtils.ageFileFilter(reference);
        final IOFileFilter filter8 = FileFilterUtils.ageFileFilter(reference, true);
        final IOFileFilter filter9 = FileFilterUtils.ageFileFilter(reference, false);

        assertFiltering(filter1, oldFile, true);
        assertFiltering(filter2, oldFile, true);
        assertFiltering(filter3, oldFile, false);
        assertFiltering(filter4, oldFile, true);
        assertFiltering(filter5, oldFile, true);
        assertFiltering(filter6, oldFile, false);
        assertFiltering(filter7, oldFile, true);
        assertFiltering(filter8, oldFile, true);
        assertFiltering(filter9, oldFile, false);
        assertFiltering(filter1, newFile, false);
        assertFiltering(filter2, newFile, false);
        assertFiltering(filter3, newFile, true);
        assertFiltering(filter4, newFile, false);
        assertFiltering(filter5, newFile, false);
        assertFiltering(filter6, newFile, true);
        assertFiltering(filter7, newFile, false);
        assertFiltering(filter8, newFile, false);
        assertFiltering(filter9, newFile, true);
        //
        assertFiltering(filter1, oldPath, true);
        assertFiltering(filter2, oldPath, true);
        assertFiltering(filter3, oldPath, false);
        assertFiltering(filter4, oldPath, true);
        assertFiltering(filter5, oldPath, true);
        assertFiltering(filter6, oldPath, false);
        assertFiltering(filter7, oldPath, true);
        assertFiltering(filter8, oldPath, true);
        assertFiltering(filter9, oldPath, false);
        assertFiltering(filter1, newPath, false);
        assertFiltering(filter2, newPath, false);
        assertFiltering(filter3, newPath, true);
        assertFiltering(filter4, newPath, false);
        assertFiltering(filter5, newPath, false);
        assertFiltering(filter6, newPath, true);
        assertFiltering(filter7, newPath, false);
        assertFiltering(filter8, newPath, false);
        assertFiltering(filter9, newPath, true);
    }

    @Test
    void testAnd() throws IOException {
        final IOFileFilter trueFilter = TrueFileFilter.INSTANCE;
        final IOFileFilter falseFilter = FalseFileFilter.INSTANCE;
        assertFiltering(trueFilter.and(trueFilter), new File("foo.test"), true);
        assertFiltering(trueFilter.and(falseFilter), new File("foo.test"), false);
        assertFiltering(falseFilter.and(trueFilter), new File("foo.test"), false);
        assertFiltering(falseFilter.and(falseFilter), new File("foo.test"), false);
    }

    @Test
    void testAnd2() throws IOException {
        final IOFileFilter trueFilter = TrueFileFilter.INSTANCE;
        final IOFileFilter falseFilter = FalseFileFilter.INSTANCE;
        assertFiltering(new AndFileFilter(trueFilter, trueFilter), new File("foo.test"), true);
        assertFiltering(new AndFileFilter(trueFilter, falseFilter), new File("foo.test"), false);
        assertFiltering(new AndFileFilter(falseFilter, trueFilter), new File("foo.test"), false);
        assertFiltering(new AndFileFilter(falseFilter, falseFilter), new File("foo.test"), false);

        final List<IOFileFilter> filters = new ArrayList<>();
        assertFiltering(new AndFileFilter(filters), new File("test"), false);
        assertFiltering(new AndFileFilter(), new File("test"), false);

        assertThrows(NullPointerException.class, () -> new AndFileFilter(falseFilter, null));
        assertThrows(NullPointerException.class, () -> new AndFileFilter(null, falseFilter));
        assertThrows(NullPointerException.class, () -> new AndFileFilter((List<IOFileFilter>) null));
    }

    @Test
    void testAndArray() throws IOException {
        final IOFileFilter trueFilter = TrueFileFilter.INSTANCE;
        final IOFileFilter falseFilter = FalseFileFilter.INSTANCE;
        assertFiltering(new AndFileFilter(trueFilter, trueFilter, trueFilter), new File("foo.test"), true);
        assertFiltering(new AndFileFilter(trueFilter, falseFilter, falseFilter), new File("foo.test"), false);
        assertFiltering(new AndFileFilter(falseFilter, trueFilter, trueFilter), new File("foo.test"), false);
        assertFiltering(new AndFileFilter(falseFilter, falseFilter, falseFilter), new File("foo.test"), false);

        final List<IOFileFilter> filters = new ArrayList<>();
        assertFiltering(new AndFileFilter(filters), new File("test"), false);
        assertFiltering(new AndFileFilter(), new File("test"), false);
    }

    @Test
    void testCanExecute() throws Exception {
        assumeTrue(SystemUtils.IS_OS_WINDOWS);
        try (TempFile executablePath = TempFile.create(getClass().getSimpleName(), null)) {
            final File executableFile = executablePath.toFile();
            try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(executablePath.get()))) {
                TestUtils.generateTestData(output, 32);
            }
            assertTrue(executableFile.setExecutable(true));
            assertFiltering(CanExecuteFileFilter.CAN_EXECUTE, executablePath.get(), true);
            assertFiltering(CanExecuteFileFilter.CAN_EXECUTE, (Path) null, false);
            assertFiltering(CanExecuteFileFilter.CAN_EXECUTE, executableFile, true);
            assertFiltering(CanExecuteFileFilter.CAN_EXECUTE, (File) null, false);
            executableFile.setExecutable(false);
            assertFiltering(CanExecuteFileFilter.CANNOT_EXECUTE, executablePath.get(), false);
            assertFiltering(CanExecuteFileFilter.CANNOT_EXECUTE, (Path) null, true);
            assertFiltering(CanExecuteFileFilter.CANNOT_EXECUTE, executableFile, false);
            assertFiltering(CanExecuteFileFilter.CANNOT_EXECUTE, (File) null, true);
        }
    }

    @Test
    void testCanRead() throws Exception {
        final File readOnlyFile = new File(temporaryFolder, "read-only-file1.txt");
        final Path readOnlyPath = readOnlyFile.toPath();
        if (!readOnlyFile.getParentFile().exists()) {
            fail("Cannot create file " + readOnlyFile + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(readOnlyFile.toPath()))) {
            TestUtils.generateTestData(output, 32);
        }
        assertTrue(readOnlyFile.setReadOnly());
        assertFiltering(CanReadFileFilter.CAN_READ, readOnlyFile, true);
        assertFiltering(CanReadFileFilter.CAN_READ, (File) null, false);
        assertFiltering(CanReadFileFilter.CAN_READ, readOnlyPath, true);
        assertFiltering(CanReadFileFilter.CAN_READ, (Path) null, false);
        assertFiltering(CanReadFileFilter.CANNOT_READ, readOnlyFile, false);
        assertFiltering(CanReadFileFilter.CANNOT_READ, (File) null, true);
        assertFiltering(CanReadFileFilter.CANNOT_READ, readOnlyPath, false);
        assertFiltering(CanReadFileFilter.CANNOT_READ, (Path) null, true);
        assertFiltering(CanReadFileFilter.READ_ONLY, readOnlyFile, true);
        assertFiltering(CanReadFileFilter.READ_ONLY, (File) null, false);
        assertFiltering(CanReadFileFilter.READ_ONLY, readOnlyPath, true);
        assertFiltering(CanReadFileFilter.READ_ONLY, (Path) null, false);
        readOnlyFile.delete();
    }

    @Test
    void testCanWrite() throws Exception {
        final File readOnlyFile = new File(temporaryFolder, "read-only-file2.txt");
        final Path readOnlyPath = readOnlyFile.toPath();
        if (!readOnlyFile.getParentFile().exists()) {
            fail("Cannot create file " + readOnlyFile + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(readOnlyFile.toPath()))) {
            TestUtils.generateTestData(output, 32);
        }
        assertTrue(readOnlyFile.setReadOnly());
        assertFiltering(CanWriteFileFilter.CAN_WRITE, temporaryFolder, true);
        assertFiltering(CanWriteFileFilter.CAN_WRITE, readOnlyFile, false);
        assertFiltering(CanWriteFileFilter.CAN_WRITE, (File) null, false);
        assertFiltering(CanWriteFileFilter.CAN_WRITE, readOnlyPath, false);
        assertFiltering(CanWriteFileFilter.CAN_WRITE, (Path) null, false);
        assertFiltering(CanWriteFileFilter.CANNOT_WRITE, temporaryFolder, false);
        assertFiltering(CanWriteFileFilter.CANNOT_WRITE, readOnlyFile, true);
        assertFiltering(CanWriteFileFilter.CANNOT_WRITE, readOnlyPath, true);
        assertFiltering(CanWriteFileFilter.CANNOT_WRITE, (File) null, true);
        assertFiltering(CanWriteFileFilter.CANNOT_WRITE, (Path) null, true);
        readOnlyFile.delete();
    }

    @Test
    void testDelegateFileFilter() throws IOException {
        final OrFileFilter orFilter = new OrFileFilter();
        final File testFile = new File("test.txt");

        IOFileFilter filter = new DelegateFileFilter((FileFilter) orFilter);
        assertFiltering(filter, testFile, false);
        assertNotNull(filter.toString()); // TODO better test

        filter = new DelegateFileFilter((FilenameFilter) orFilter);
        assertFiltering(filter, testFile, false);
        assertNotNull(filter.toString()); // TODO better test

        assertThrows(NullPointerException.class, () -> new DelegateFileFilter((FileFilter) null));
        assertThrows(NullPointerException.class, () -> new DelegateFileFilter((FilenameFilter) null));
    }

    @Test
    void testDelegation() { // TODO improve these tests
        assertNotNull(FileFilterUtils.asFileFilter((FileFilter) FalseFileFilter.INSTANCE));
        assertNotNull(FileFilterUtils.asFileFilter((FilenameFilter) FalseFileFilter.INSTANCE).toString());
    }

    @SuppressWarnings("deprecation")
    @Test
    void testDeprecatedWildcard() throws IOException {
        IOFileFilter filter = new WildcardFilter("*.txt");
        final List<String> patternList = Arrays.asList("*.txt", "*.xml", "*.gif");
        final IOFileFilter listFilter = new WildcardFilter(patternList);
        final File txtFile = new File("test.txt");
        final Path txtPath = txtFile.toPath();
        final File bmpFile = new File("test.bmp");
        final Path bmpPath = bmpFile.toPath();
        final File dirFile = new File("src/java");
        final Path dirPath = dirFile.toPath();

        assertFiltering(filter, new File("log.txt"), true);
//        assertFiltering(filter, new File("log.txt.bak"), false);
        assertFiltering(filter, new File("log.txt").toPath(), true);

        filter = new WildcardFilter("log?.txt");
        assertFiltering(filter, new File("log1.txt"), true);
        assertFiltering(filter, new File("log12.txt"), false);
        //
        assertFiltering(filter, new File("log1.txt").toPath(), true);
        assertFiltering(filter, new File("log12.txt").toPath(), false);

        filter = new WildcardFilter("open??.????04");
        assertFiltering(filter, new File("openAB.102504"), true);
        assertFiltering(filter, new File("openA.102504"), false);
        assertFiltering(filter, new File("openXY.123103"), false);
//        assertFiltering(filter, new File("openAB.102504.old"), false);
        //
        assertFiltering(filter, new File("openAB.102504").toPath(), true);
        assertFiltering(filter, new File("openA.102504").toPath(), false);
        assertFiltering(filter, new File("openXY.123103").toPath(), false);
//        assertFiltering(filter, new File("openAB.102504.old").toPath(), false);

        filter = new WildcardFilter("*.java", "*.class");
        assertFiltering(filter, new File("Test.java"), true);
        assertFiltering(filter, new File("Test.class"), true);
        assertFiltering(filter, new File("Test.jsp"), false);
        //
        assertFiltering(filter, new File("Test.java").toPath(), true);
        assertFiltering(filter, new File("Test.class").toPath(), true);
        assertFiltering(filter, new File("Test.jsp").toPath(), false);

        assertFiltering(listFilter, new File("Test.txt"), true);
        assertFiltering(listFilter, new File("Test.xml"), true);
        assertFiltering(listFilter, new File("Test.gif"), true);
        assertFiltering(listFilter, new File("Test.bmp"), false);
        //
        assertFiltering(listFilter, new File("Test.txt").toPath(), true);
        assertFiltering(listFilter, new File("Test.xml").toPath(), true);
        assertFiltering(listFilter, new File("Test.gif").toPath(), true);
        assertFiltering(listFilter, new File("Test.bmp").toPath(), false);

        // File
        assertTrue(listFilter.accept(txtFile));
        assertFalse(listFilter.accept(bmpFile));
        assertFalse(listFilter.accept(dirFile));
        // Path
        assertTrue(listFilter.matches(txtPath));
        assertFalse(listFilter.matches(bmpPath));
        assertFalse(listFilter.matches(dirPath));
        //
        assertEquals(FileVisitResult.CONTINUE, listFilter.accept(txtPath, null));
        assertEquals(FileVisitResult.TERMINATE, listFilter.accept(bmpPath, null));
        assertEquals(FileVisitResult.TERMINATE, listFilter.accept(dirPath, null));

        assertTrue(listFilter.accept(txtFile.getParentFile(), txtFile.getName()));
        assertFalse(listFilter.accept(bmpFile.getParentFile(), bmpFile.getName()));
        assertFalse(listFilter.accept(dirFile.getParentFile(), dirFile.getName()));
        //
        assertEquals(FileVisitResult.CONTINUE, listFilter.accept(txtPath, null));
        assertEquals(FileVisitResult.TERMINATE, listFilter.accept(bmpPath, null));
        assertEquals(FileVisitResult.TERMINATE, listFilter.accept(dirPath, null));

        assertThrows(NullPointerException.class, () -> new WildcardFilter((String) null));
        assertThrows(NullPointerException.class, () -> new WildcardFilter((String[]) null));
        assertThrows(NullPointerException.class, () -> new WildcardFilter((List<String>) null));
    }

    @Test
    void testDirectory() throws IOException {
        // XXX: This test presumes the current working dir is the base dir of the source checkout.
        final IOFileFilter filter = new DirectoryFileFilter();

        assertFiltering(filter, new File("src/"), true);
        assertFiltering(filter, new File("src/").toPath(), true);
        assertFiltering(filter, new File("src/main/java/"), true);
        assertFiltering(filter, new File("src/main/java/").toPath(), true);

        assertFiltering(filter, new File("pom.xml"), false);
        assertFiltering(filter, new File("pom.xml").toPath(), false);

        assertFiltering(filter, new File("imaginary"), false);
        assertFiltering(filter, new File("imaginary").toPath(), false);
        assertFiltering(filter, new File("imaginary/"), false);
        assertFiltering(filter, new File("imaginary/").toPath(), false);

        assertFiltering(filter, new File("LICENSE.txt"), false);
        assertFiltering(filter, new File("LICENSE.txt").toPath(), false);

        assertFiltering(filter, (File) null, false);
        assertFiltering(filter, (Path) null, false);

        assertSame(DirectoryFileFilter.DIRECTORY, DirectoryFileFilter.INSTANCE);
    }

    @Test
    void testEmpty() throws Exception {

        // Empty Dir
        final File emptyDirFile = new File(temporaryFolder, "empty-dir");
        final Path emptyDirPath = emptyDirFile.toPath();
        emptyDirFile.mkdirs();
        assertFiltering(EmptyFileFilter.EMPTY, emptyDirFile, true);
        assertFiltering(EmptyFileFilter.EMPTY, emptyDirPath, true);
        assertFiltering(EmptyFileFilter.EMPTY, (File) null, true);
        assertFiltering(EmptyFileFilter.EMPTY, (Path) null, true);
        assertFiltering(EmptyFileFilter.NOT_EMPTY, emptyDirFile, false);
        assertFiltering(EmptyFileFilter.NOT_EMPTY, emptyDirPath, false);
        assertFiltering(EmptyFileFilter.NOT_EMPTY, (File) null, false);
        assertFiltering(EmptyFileFilter.NOT_EMPTY, (Path) null, false);

        // Empty File
        final File emptyFile = new File(emptyDirFile, "empty-file.txt");
        final Path emptyPath = emptyFile.toPath();
        if (!emptyFile.getParentFile().exists()) {
            fail("Cannot create file " + emptyFile + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output1 = new BufferedOutputStream(Files.newOutputStream(emptyFile.toPath()))) {
            TestUtils.generateTestData(output1, 0);
        }
        assertFiltering(EmptyFileFilter.EMPTY, emptyFile, true);
        assertFiltering(EmptyFileFilter.EMPTY, emptyPath, true);
        assertFiltering(EmptyFileFilter.NOT_EMPTY, emptyFile, false);
        assertFiltering(EmptyFileFilter.NOT_EMPTY, emptyPath, false);

        // Not Empty Dir
        assertFiltering(EmptyFileFilter.EMPTY, emptyDirFile, false);
        assertFiltering(EmptyFileFilter.EMPTY, emptyDirPath, false);
        assertFiltering(EmptyFileFilter.NOT_EMPTY, emptyDirFile, true);
        assertFiltering(EmptyFileFilter.NOT_EMPTY, emptyDirPath, true);

        // Not Empty File
        final File notEmptyFile = new File(emptyDirFile, "not-empty-file.txt");
        final Path notEmptyPath = notEmptyFile.toPath();
        if (!notEmptyFile.getParentFile().exists()) {
            fail("Cannot create file " + notEmptyFile + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(notEmptyFile.toPath()))) {
            TestUtils.generateTestData(output, 32);
        }
        assertFiltering(EmptyFileFilter.EMPTY, notEmptyFile, false);
        assertFiltering(EmptyFileFilter.EMPTY, notEmptyPath, false);
        assertFiltering(EmptyFileFilter.NOT_EMPTY, notEmptyFile, true);
        assertFiltering(EmptyFileFilter.NOT_EMPTY, notEmptyPath, true);
        FileUtils.forceDelete(emptyDirFile);
    }

    @Test
    void testEnsureTestCoverage() {
        assertNotNull(new FileFilterUtils()); // dummy for test coverage
    }

    @Test
    void testFalse() throws IOException {
        final IOFileFilter filter = FileFilterUtils.falseFileFilter();
        assertFiltering(filter, new File("foo.test"), false);
        assertFiltering(filter, new File("foo.test").toPath(), false);
        assertFiltering(filter, new File("foo"), false);
        assertFiltering(filter, new File("foo").toPath(), false);
        assertFiltering(filter, (File) null, false);
        assertFiltering(filter, (Path) null, false);
        assertSame(FalseFileFilter.FALSE, FalseFileFilter.INSTANCE);
        assertSame(TrueFileFilter.TRUE, FalseFileFilter.INSTANCE.negate());
        assertSame(TrueFileFilter.INSTANCE, FalseFileFilter.INSTANCE.negate());
        assertNotNull(FalseFileFilter.INSTANCE.toString());
    }

    @Test
    void testFileEqualsFilter() throws IOException {
        assertFooBarFileFiltering(
            new FileEqualsFileFilter(new File("foo")).or(new FileEqualsFileFilter(new File("bar"))));
    }

    @Test
    void testFileFilterUtils_and() throws IOException {
        final IOFileFilter trueFilter = TrueFileFilter.INSTANCE;
        final IOFileFilter falseFilter = FalseFileFilter.INSTANCE;
        assertFiltering(FileFilterUtils.and(trueFilter, trueFilter, trueFilter), new File("foo.test"), true);
        assertFiltering(FileFilterUtils.and(trueFilter, falseFilter, trueFilter), new File("foo.test"), false);
        assertFiltering(FileFilterUtils.and(falseFilter, trueFilter), new File("foo.test"), false);
        assertFiltering(FileFilterUtils.and(falseFilter, falseFilter), new File("foo.test"), false);
    }

    @Test
    void testFileFilterUtils_or() throws IOException {
        final IOFileFilter trueFilter = TrueFileFilter.INSTANCE;
        final IOFileFilter falseFilter = FalseFileFilter.INSTANCE;
        final File testFile = new File("foo.test");
        assertFiltering(FileFilterUtils.or(trueFilter, trueFilter), testFile, true);
        assertFiltering(FileFilterUtils.or(trueFilter, trueFilter, falseFilter), testFile, true);
        assertFiltering(FileFilterUtils.or(falseFilter, trueFilter), testFile, true);
        assertFiltering(FileFilterUtils.or(falseFilter, falseFilter, falseFilter), testFile, false);
    }

    @Test
    void testFiles() throws IOException {
        // XXX: This test presumes the current working dir is the base dir of the source checkout.
        final IOFileFilter filter = FileFileFilter.INSTANCE;

        assertFiltering(filter, (File) null, false);
        assertFiltering(filter, new File("src/"), false);
        assertFiltering(filter, new File("src/").toPath(), false);
        assertFiltering(filter, new File("src/java/"), false);
        assertFiltering(filter, new File("src/java/").toPath(), false);

        assertFiltering(filter, new File("pom.xml"), true);
        assertFiltering(filter, new File("pom.xml").toPath(), true);

        assertFiltering(filter, new File("imaginary"), false);
        assertFiltering(filter, new File("imaginary").toPath(), false);
        assertFiltering(filter, new File("imaginary/"), false);
        assertFiltering(filter, new File("imaginary/").toPath(), false);

        assertFiltering(filter, new File("LICENSE.txt"), true);
        assertFiltering(filter, new File("LICENSE.txt").toPath(), true);
    }

    /*
     * Test method for {@link FileFilterUtils#filter(IOFileFilter, java.lang.Iterable)} that tests that the method
     * properly filters files from the list.
     */
    @Test
    void testFilterArray_fromList() throws Exception {
        final File fileA = TestUtils.newFile(temporaryFolder, "A");
        final File fileB = TestUtils.newFile(temporaryFolder, "B");
        final List<File> fileList = Arrays.asList(fileA, fileB);

        final IOFileFilter filter = FileFilterUtils.nameFileFilter("A");

        final File[] filtered = FileFilterUtils.filter(filter, fileList);

        assertEquals(1, filtered.length);
        assertEquals(fileA, filtered[0]);
    }

    /*
     * Test method for {@link FileFilterUtils#filter(IOFileFilter, File...)} that tests that the method properly filters
     * files from the list.
     */
    @Test
    void testFilterArray_IOFileFilter() throws Exception {
        final File fileA = TestUtils.newFile(temporaryFolder, "A");
        final File fileB = TestUtils.newFile(temporaryFolder, "B");

        final IOFileFilter filter = FileFilterUtils.nameFileFilter("A");

        final File[] filtered = FileFilterUtils.filter(filter, fileA, fileB);

        assertEquals(1, filtered.length);
        assertEquals(fileA, filtered[0]);
    }

    /*
     * Test method for {@link FileFilterUtils#filter(IOFileFilter, File...)} that tests that the method properly filters
     * files from the list.
     */
    @Test
    void testFilterArray_PathVisitorFileFilter_FileExistsNo() throws Exception {
        final File fileA = TestUtils.newFile(temporaryFolder, "A");
        final File fileB = TestUtils.newFile(temporaryFolder, "B");

        final IOFileFilter filter = new PathVisitorFileFilter(new NameFileFilter("A"));

        final File[] filtered = FileFilterUtils.filter(filter, fileA, fileB);

        assertEquals(1, filtered.length);
        assertEquals(fileA, filtered[0]);
    }

    /*
     * Test method for {@link FileFilterUtils#filter(IOFileFilter, File...)} that tests that the method properly filters
     * files from the list.
     */
    @Test
    void testFilterArray_PathVisitorFileFilter_FileExistsYes() throws Exception {
        final File fileA = TestUtils.newFile(temporaryFolder, "A");
        final File fileB = TestUtils.newFile(temporaryFolder, "B");
        FileUtils.write(fileA, "test", StandardCharsets.US_ASCII);

        final IOFileFilter filter = new PathVisitorFileFilter(new NameFileFilter("A"));

        final File[] filtered = FileFilterUtils.filter(filter, fileA, fileB);

        assertEquals(1, filtered.length);
        assertEquals(fileA, filtered[0]);
    }

    /*
     * Test method for {@link FileFilterUtils#filter(IOFileFilter, File...)} that tests {@code null} parameters.
     */
    @Test
    void testFilterFilesArrayNullParameters() throws Exception {
        final File fileA = TestUtils.newFile(temporaryFolder, "A");
        final File fileB = TestUtils.newFile(temporaryFolder, "B");
        assertThrows(NullPointerException.class, () -> FileFilterUtils.filter(null, fileA, fileB));

        final IOFileFilter filter = FileFilterUtils.trueFileFilter();
        FileFilterUtils.filter(filter, fileA, null);

        final File[] filtered = FileFilterUtils.filter(filter, (File[]) null);
        assertEquals(0, filtered.length);
    }

    /*
     * Test method for {@link FileFilterUtils#filterList(IOFileFilter, java.lang.Iterable)} that tests that the method
     * properly filters files from the list.
     */
    @Test
    void testFilterList() throws Exception {
        final File fileA = TestUtils.newFile(temporaryFolder, "A");
        final File fileB = TestUtils.newFile(temporaryFolder, "B");
        final List<File> fileList = Arrays.asList(fileA, fileB);

        final IOFileFilter filter = FileFilterUtils.nameFileFilter("A");

        final List<File> filteredList = FileFilterUtils.filterList(filter, fileList);

        assertTrue(filteredList.contains(fileA));
        assertFalse(filteredList.contains(fileB));
    }

    /*
     * Test method for {@link FileFilterUtils#filterList(IOFileFilter, File...)} that tests that the method properly
     * filters files from the list.
     */
    @Test
    void testFilterList_fromArray() throws Exception {
        final File fileA = TestUtils.newFile(temporaryFolder, "A");
        final File fileB = TestUtils.newFile(temporaryFolder, "B");

        final IOFileFilter filter = FileFilterUtils.nameFileFilter("A");

        final List<File> filteredList = FileFilterUtils.filterList(filter, fileA, fileB);

        assertTrue(filteredList.contains(fileA));
        assertFalse(filteredList.contains(fileB));
    }

    /*
     * Test method for {@link FileFilterUtils#filterList(IOFileFilter, java.lang.Iterable)} that tests {@code null}
     * parameters and {@code null} elements in the provided list.
     */
    @Test
    void testFilterListNullParameters() {
        assertThrows(NullPointerException.class, () -> FileFilterUtils.filterList(null, Collections.emptyList()));

        final IOFileFilter filter = FileFilterUtils.trueFileFilter();
        List<File> filteredList = FileFilterUtils.filterList(filter, Collections.singletonList(null));
        assertEquals(1, filteredList.size());
        assertNull(filteredList.get(0));

        filteredList = FileFilterUtils.filterList(filter, (List<File>) null);
        assertEquals(0, filteredList.size());
    }

    /*
     * Test method for {@link FileFilterUtils#filter(IOFileFilter, Path...)}.
     */
    @Test
    void testFilterPathsArrayNullParameters() throws Exception {
        final Path fileA = TestUtils.newFile(temporaryFolder, "A").toPath();
        final Path fileB = TestUtils.newFile(temporaryFolder, "B").toPath();
        assertThrows(NullPointerException.class, () -> PathUtils.filter(null, fileA, fileB));

        final IOFileFilter filter = FileFilterUtils.trueFileFilter();
        PathUtils.filter(filter, fileA, null);

        final File[] filtered = FileFilterUtils.filter(filter, (File[]) null);
        assertEquals(0, filtered.length);
    }

    /*
     * Test method for {@link FileFilterUtils#filterSet(IOFileFilter, java.lang.Iterable)} that tests that the method
     * properly filters files from the set.
     */
    @Test
    void testFilterSet() throws Exception {
        final File fileA = TestUtils.newFile(temporaryFolder, "A");
        final File fileB = TestUtils.newFile(temporaryFolder, "B");
        final Set<File> fileList = new HashSet<>(Arrays.asList(fileA, fileB));

        final IOFileFilter filter = FileFilterUtils.nameFileFilter("A");

        final Set<File> filteredSet = FileFilterUtils.filterSet(filter, fileList);

        assertTrue(filteredSet.contains(fileA));
        assertFalse(filteredSet.contains(fileB));
    }

    /*
     * Test method for {@link FileFilterUtils#filterSet(IOFileFilter, File...)} that tests that the method properly
     * filters files from the set.
     */
    @Test
    void testFilterSet_fromArray() throws Exception {
        final File fileA = TestUtils.newFile(temporaryFolder, "A");
        final File fileB = TestUtils.newFile(temporaryFolder, "B");

        final IOFileFilter filter = FileFilterUtils.nameFileFilter("A");

        final Set<File> filteredSet = FileFilterUtils.filterSet(filter, fileA, fileB);

        assertTrue(filteredSet.contains(fileA));
        assertFalse(filteredSet.contains(fileB));
    }

    /*
     * Test method for {@link FileFilterUtils#filterSet(IOFileFilter, java.lang.Iterable)} that tests {@code null}
     * parameters and {@code null} elements in the provided set.
     */
    @Test
    void testFilterSetNullParameters() {
        assertThrows(NullPointerException.class, () -> FileFilterUtils.filterSet(null, Collections.emptySet()));

        final IOFileFilter filter = FileFilterUtils.trueFileFilter();
        FileFilterUtils.filterSet(filter, new HashSet<>(Collections.singletonList(null)));

        final Set<File> filteredSet = FileFilterUtils.filterSet(filter, (Set<File>) null);
        assertEquals(0, filteredSet.size());
    }

    @Test
    void testHidden() throws IOException {
        final File hiddenDirFile = new File(SVN_DIR_NAME);
        final Path hiddenDirPath = hiddenDirFile.toPath();
        if (hiddenDirFile.exists()) {
            assertFiltering(HiddenFileFilter.HIDDEN, hiddenDirFile, hiddenDirFile.isHidden());
            assertFiltering(HiddenFileFilter.HIDDEN, hiddenDirPath, hiddenDirFile.isHidden());
            assertFiltering(HiddenFileFilter.VISIBLE, hiddenDirFile, !hiddenDirFile.isHidden());
            assertFiltering(HiddenFileFilter.VISIBLE, hiddenDirPath, !hiddenDirFile.isHidden());
        }
        final Path path = temporaryFolder.toPath();
        assertFiltering(HiddenFileFilter.HIDDEN, temporaryFolder, false);
        assertFiltering(HiddenFileFilter.HIDDEN, path, false);
        assertFiltering(HiddenFileFilter.HIDDEN, (Path) null, true);
        assertFiltering(HiddenFileFilter.VISIBLE, temporaryFolder, true);
        assertFiltering(HiddenFileFilter.VISIBLE, path, true);
        assertFiltering(HiddenFileFilter.VISIBLE, (Path) null, false);
    }

    @Test
    void testMagicNumberFileFilterBytes() throws Exception {
        final byte[] classFileMagicNumber = {(byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE};
        final String xmlFileContent = "<?xml version=\"1.0\" encoding=\"UTF-8\">\n" + "<element>text</element>";

        final File classAFile = new File(temporaryFolder, "A.class");
        final Path classAPath = classAFile.toPath();
        final File xmlBFile = new File(temporaryFolder, "B.xml");
        final Path xmlBPath = xmlBFile.toPath();
        final File emptyFile = new File(temporaryFolder, "C.xml");
        final Path emptyPath = emptyFile.toPath();
        final File dirFile = new File(temporaryFolder, "D");
        final Path dirPath = dirFile.toPath();
        dirFile.mkdirs();

        try (OutputStream classFileAStream = FileUtils.openOutputStream(classAFile)) {
            IOUtils.write(classFileMagicNumber, classFileAStream);
            TestUtils.generateTestData(classFileAStream, 32);
        }

        FileUtils.write(xmlBFile, xmlFileContent, StandardCharsets.UTF_8);
        FileUtils.touch(emptyFile);

        IOFileFilter filter = new MagicNumberFileFilter(classFileMagicNumber);

        assertFiltering(filter, classAFile, true);
        assertFiltering(filter, classAPath, true);
        assertFiltering(filter, xmlBFile, false);
        assertFiltering(filter, xmlBPath, false);
        assertFiltering(filter, emptyFile, false);
        assertFiltering(filter, emptyPath, false);
        assertFiltering(filter, dirFile, false);
        assertFiltering(filter, dirPath, false);

        filter = FileFilterUtils.magicNumberFileFilter(classFileMagicNumber);

        assertFiltering(filter, classAFile, true);
        assertFiltering(filter, classAPath, true);
        assertFiltering(filter, xmlBFile, false);
        assertFiltering(filter, xmlBPath, false);
        assertFiltering(filter, emptyFile, false);
        assertFiltering(filter, emptyPath, false);
        assertFiltering(filter, dirFile, false);
        assertFiltering(filter, dirPath, false);
    }

    @Test
    void testMagicNumberFileFilterBytesOffset() throws Exception {
        final byte[] tarMagicNumber = {0x75, 0x73, 0x74, 0x61, 0x72};
        final long tarMagicNumberOffset = 257;

        final File tarFileA = new File(temporaryFolder, "A.tar");
        final File randomFileB = new File(temporaryFolder, "B.txt");
        final File dir = new File(temporaryFolder, "D");
        dir.mkdirs();

        try (OutputStream tarFileAStream = FileUtils.openOutputStream(tarFileA)) {
            TestUtils.generateTestData(tarFileAStream, tarMagicNumberOffset);
            IOUtils.write(tarMagicNumber, tarFileAStream);
        }

        if (!randomFileB.getParentFile().exists()) {
            fail("Cannot create file " + randomFileB + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(randomFileB.toPath()))) {
            TestUtils.generateTestData(output, 2 * tarMagicNumberOffset);
        }

        IOFileFilter filter = new MagicNumberFileFilter(tarMagicNumber, tarMagicNumberOffset);

        assertFiltering(filter, tarFileA, true);
        assertFiltering(filter, randomFileB, false);
        assertFiltering(filter, dir, false);

        filter = FileFilterUtils.magicNumberFileFilter(tarMagicNumber, tarMagicNumberOffset);

        assertFiltering(filter, tarFileA, true);
        assertFiltering(filter, randomFileB, false);
        assertFiltering(filter, dir, false);
    }

    @Test
    void testMagicNumberFileFilterString() throws Exception {
        final byte[] classFileMagicNumber = {(byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE};
        final String xmlFileContent = "<?xml version=\"1.0\" encoding=\"UTF-8\">\n" + "<element>text</element>";
        final String xmlMagicNumber = "<?xml version=\"1.0\"";

        final File classFileA = new File(temporaryFolder, "A.class");
        final File xmlFileB = new File(temporaryFolder, "B.xml");
        final File dir = new File(temporaryFolder, "D");
        dir.mkdirs();

        try (OutputStream classFileAStream = FileUtils.openOutputStream(classFileA)) {
            IOUtils.write(classFileMagicNumber, classFileAStream);
            TestUtils.generateTestData(classFileAStream, 32);
        }

        FileUtils.write(xmlFileB, xmlFileContent, StandardCharsets.UTF_8);

        IOFileFilter filter = new MagicNumberFileFilter(xmlMagicNumber);

        assertFiltering(filter, classFileA, false);
        assertFiltering(filter, xmlFileB, true);
        assertFiltering(filter, dir, false);

        filter = FileFilterUtils.magicNumberFileFilter(xmlMagicNumber);

        assertFiltering(filter, classFileA, false);
        assertFiltering(filter, xmlFileB, true);
        assertFiltering(filter, dir, false);
    }

    @Test
    void testMagicNumberFileFilterStringOffset() throws Exception {
        final String tarMagicNumber = "ustar";
        final long tarMagicNumberOffset = 257;

        final File tarFileA = new File(temporaryFolder, "A.tar");
        final File randomFileB = new File(temporaryFolder, "B.txt");
        final File dir = new File(temporaryFolder, "D");
        dir.mkdirs();

        try (OutputStream tarFileAStream = FileUtils.openOutputStream(tarFileA)) {
            TestUtils.generateTestData(tarFileAStream, tarMagicNumberOffset);
            IOUtils.write(tarMagicNumber, tarFileAStream, StandardCharsets.UTF_8);
        }

        if (!randomFileB.getParentFile().exists()) {
            fail("Cannot create file " + randomFileB + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(randomFileB.toPath()))) {
            TestUtils.generateTestData(output, 2 * tarMagicNumberOffset);
        }

        IOFileFilter filter = new MagicNumberFileFilter(tarMagicNumber, tarMagicNumberOffset);

        assertFiltering(filter, tarFileA, true);
        assertFiltering(filter, randomFileB, false);
        assertFiltering(filter, dir, false);

        filter = FileFilterUtils.magicNumberFileFilter(tarMagicNumber, tarMagicNumberOffset);

        assertFiltering(filter, tarFileA, true);
        assertFiltering(filter, randomFileB, false);
        assertFiltering(filter, dir, false);
    }

    @Test
    void testMagicNumberFileFilterValidation() {
        assertThrows(NullPointerException.class, () -> new MagicNumberFileFilter((String) null, 0));
        assertThrows(IllegalArgumentException.class, () -> new MagicNumberFileFilter("0", -1));
        assertThrows(IllegalArgumentException.class, () -> new MagicNumberFileFilter("", 0));
        assertThrows(NullPointerException.class, () -> new MagicNumberFileFilter((byte[]) null, 0));
        assertThrows(IllegalArgumentException.class, () -> new MagicNumberFileFilter(new byte[] {0}, -1));
        assertThrows(IllegalArgumentException.class, () -> new MagicNumberFileFilter(new byte[] {}, 0));
    }

    @Test
    void testMakeCVSAware() throws Exception {
        final IOFileFilter filter1 = FileFilterUtils.makeCVSAware(null);
        final IOFileFilter filter2 = FileFilterUtils.makeCVSAware(FileFilterUtils.nameFileFilter("test-file1.txt"));

        File file = new File(temporaryFolder, "CVS");
        file.mkdirs();
        assertFiltering(filter1, file, false);
        assertFiltering(filter2, file, false);
        FileUtils.deleteDirectory(file);

        file = new File(temporaryFolder, "test-file1.txt");
        if (!file.getParentFile().exists()) {
            fail("Cannot create file " + file + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output2 = new BufferedOutputStream(Files.newOutputStream(file.toPath()))) {
            TestUtils.generateTestData(output2, 0);
        }
        assertFiltering(filter1, file, true);
        assertFiltering(filter2, file, true);

        file = new File(temporaryFolder, "test-file2.log");
        if (!file.getParentFile().exists()) {
            fail("Cannot create file " + file + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output1 = new BufferedOutputStream(Files.newOutputStream(file.toPath()))) {
            TestUtils.generateTestData(output1, 0);
        }
        assertFiltering(filter1, file, true);
        assertFiltering(filter2, file, false);

        file = new File(temporaryFolder, "CVS");
        if (!file.getParentFile().exists()) {
            fail("Cannot create file " + file + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(file.toPath()))) {
            TestUtils.generateTestData(output, 0);
        }
        assertFiltering(filter1, file, true);
        assertFiltering(filter2, file, false);
    }

    @Test
    void testMakeDirectoryOnly() throws Exception {
        assertSame(DirectoryFileFilter.DIRECTORY, FileFilterUtils.makeDirectoryOnly(null));

        final IOFileFilter filter = FileFilterUtils.makeDirectoryOnly(FileFilterUtils.nameFileFilter("B"));

        final File fileA = new File(temporaryFolder, "A");
        final File fileB = new File(temporaryFolder, "B");

        fileA.mkdirs();
        fileB.mkdirs();

        assertFiltering(filter, fileA, false);
        assertFiltering(filter, fileB, true);

        FileUtils.deleteDirectory(fileA);
        FileUtils.deleteDirectory(fileB);

        if (!fileA.getParentFile().exists()) {
            fail("Cannot create file " + fileA + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output1 = new BufferedOutputStream(Files.newOutputStream(fileA.toPath()))) {
            TestUtils.generateTestData(output1, 32);
        }
        if (!fileB.getParentFile().exists()) {
            fail("Cannot create file " + fileB + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(fileB.toPath()))) {
            TestUtils.generateTestData(output, 32);
        }

        assertFiltering(filter, fileA, false);
        assertFiltering(filter, fileB, false);

        fileA.delete();
        fileB.delete();
    }

    @Test
    void testMakeFileOnly() throws Exception {
        assertSame(FileFileFilter.INSTANCE, FileFilterUtils.makeFileOnly(null));

        final IOFileFilter filter = FileFilterUtils.makeFileOnly(FileFilterUtils.nameFileFilter("B"));

        final File fileA = new File(temporaryFolder, "A");
        final File fileB = new File(temporaryFolder, "B");

        fileA.mkdirs();
        fileB.mkdirs();

        assertFiltering(filter, fileA, false);
        assertFiltering(filter, fileB, false);

        FileUtils.deleteDirectory(fileA);
        FileUtils.deleteDirectory(fileB);

        if (!fileA.getParentFile().exists()) {
            fail("Cannot create file " + fileA + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output1 = new BufferedOutputStream(Files.newOutputStream(fileA.toPath()))) {
            TestUtils.generateTestData(output1, 32);
        }
        if (!fileB.getParentFile().exists()) {
            fail("Cannot create file " + fileB + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(fileB.toPath()))) {
            TestUtils.generateTestData(output, 32);
        }

        assertFiltering(filter, fileA, false);
        assertFiltering(filter, fileB, true);

        fileA.delete();
        fileB.delete();
    }

    @Test
    void testMakeSVNAware() throws Exception {
        final IOFileFilter filter1 = FileFilterUtils.makeSVNAware(null);
        final IOFileFilter filter2 = FileFilterUtils.makeSVNAware(FileFilterUtils.nameFileFilter("test-file1.txt"));

        File file = new File(temporaryFolder, SVN_DIR_NAME);
        file.mkdirs();
        assertFiltering(filter1, file, false);
        assertFiltering(filter2, file, false);
        FileUtils.deleteDirectory(file);

        file = new File(temporaryFolder, "test-file1.txt");
        if (!file.getParentFile().exists()) {
            fail("Cannot create file " + file + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output2 = new BufferedOutputStream(Files.newOutputStream(file.toPath()))) {
            TestUtils.generateTestData(output2, 0);
        }
        assertFiltering(filter1, file, true);
        assertFiltering(filter2, file, true);

        file = new File(temporaryFolder, "test-file2.log");
        if (!file.getParentFile().exists()) {
            fail("Cannot create file " + file + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output1 = new BufferedOutputStream(Files.newOutputStream(file.toPath()))) {
            TestUtils.generateTestData(output1, 0);
        }
        assertFiltering(filter1, file, true);
        assertFiltering(filter2, file, false);

        file = new File(temporaryFolder, SVN_DIR_NAME);
        if (!file.getParentFile().exists()) {
            fail("Cannot create file " + file + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(file.toPath()))) {
            TestUtils.generateTestData(output, 0);
        }
        assertFiltering(filter1, file, true);
        assertFiltering(filter2, file, false);
    }

    @Test
    void testNameFilter() throws IOException {
        final NameFileFilter filter = new NameFileFilter("foo", "bar");
        assertFooBarFileFiltering(filter);
        assertFiltering(filter, (File) null, false);
        assertFiltering(filter, (Path) null, false);
    }

    @Test
    void testNameFilterNullArgument() {
        final String test = null;
        final String failMessage = "constructing a NameFileFilter with a null String argument should fail.";
        assertThrows(NullPointerException.class, () -> new NameFileFilter(test), failMessage);
        assertThrows(NullPointerException.class, () -> FileFilterUtils.nameFileFilter(test, IOCase.INSENSITIVE), failMessage);
    }

    @Test
    void testNameFilterNullArrayArgument() {
        assertThrows(NullPointerException.class, () -> new NameFileFilter((String[]) null));
    }

    @Test
    void testNameFilterNullListArgument() {
        final List<String> test = null;
        assertThrows(NullPointerException.class, () -> new NameFileFilter(test));
    }

    @Test
    void testNegate() throws IOException {
        final IOFileFilter filter = FileFilterUtils.notFileFilter(FileFilterUtils.trueFileFilter());
        assertFiltering(filter, new File("foo.test"), false);
        assertFiltering(filter, new File("foo"), false);
        assertFiltering(filter.negate(), new File("foo"), true);
        assertFiltering(filter, (File) null, false);
        assertThrows(NullPointerException.class, () -> new NotFileFilter(null));
    }

    @Test
    void testNullFilters() {
        assertThrows(NullPointerException.class, () -> FileFilterUtils.toList((IOFileFilter) null));
        assertThrows(NullPointerException.class, () -> FileFilterUtils.toList(new IOFileFilter[] {null}));
    }

    @Test
    void testOr() throws IOException {
        final IOFileFilter trueFilter = TrueFileFilter.INSTANCE;
        final IOFileFilter falseFilter = FalseFileFilter.INSTANCE;
        final File testFile = new File("foo.test");
        final Path testPath = testFile.toPath();
        assertFiltering(new OrFileFilter(trueFilter, trueFilter), testFile, true);
        assertFiltering(new OrFileFilter(trueFilter, falseFilter), testFile, true);
        assertFiltering(new OrFileFilter(falseFilter, trueFilter), testFile, true);
        assertFiltering(new OrFileFilter(falseFilter, falseFilter), testFile, false);
        assertFiltering(new OrFileFilter(), testFile, false);
        //
        assertFiltering(new OrFileFilter(trueFilter, trueFilter), testPath, true);
        assertFiltering(new OrFileFilter(trueFilter, falseFilter), testPath, true);
        assertFiltering(new OrFileFilter(falseFilter, trueFilter), testPath, true);
        assertFiltering(new OrFileFilter(falseFilter, falseFilter), testPath, false);
        assertFiltering(new OrFileFilter(), testPath, false);
        //
        assertFiltering(falseFilter.or(trueFilter), testPath, true);

        final List<IOFileFilter> filters = new ArrayList<>();
        filters.add(trueFilter);
        filters.add(falseFilter);

        final OrFileFilter orFilter = new OrFileFilter(filters);

        assertFiltering(orFilter, testFile, true);
        assertFiltering(orFilter, testPath, true);
        assertEquals(orFilter.getFileFilters(), filters);
        orFilter.removeFileFilter(trueFilter);
        assertFiltering(orFilter, testFile, false);
        assertFiltering(orFilter, testPath, false);
        orFilter.setFileFilters(filters);
        assertFiltering(orFilter, testFile, true);
        assertFiltering(orFilter, testPath, true);

        assertTrue(orFilter.accept(testFile.getParentFile(), testFile.getName()));
        assertEquals(FileVisitResult.CONTINUE, orFilter.accept(testPath, null));
        assertTrue(orFilter.matches(testPath));
        orFilter.removeFileFilter(trueFilter);
        assertFalse(orFilter.accept(testFile.getParentFile(), testFile.getName()));
        assertEquals(FileVisitResult.TERMINATE, orFilter.accept(testPath, null));
        assertFalse(orFilter.matches(testPath));

        assertThrows(NullPointerException.class, () -> new OrFileFilter(falseFilter, null));
    }

    @Test
    void testPathEqualsFilter() throws IOException {
        assertFooBarFileFiltering(
            new PathEqualsFileFilter(Paths.get("foo")).or(new PathEqualsFileFilter(Paths.get("bar"))));
    }

    @Test
    void testPrefix() throws IOException {
        IOFileFilter filter = new PrefixFileFilter("foo", "bar");
        final File testFile = new File("test");
        final Path testPath = testFile.toPath();
        final File fredFile = new File("fred");
        final Path fredPath = fredFile.toPath();

        assertFiltering(filter, new File("foo.test"), true);
        assertFiltering(filter, new File("FOO.test"), false); // case-sensitive
        assertFiltering(filter, new File("foo"), true);
        assertFiltering(filter, new File("bar"), true);
        assertFiltering(filter, new File("food/"), true);
        //
        assertFiltering(filter, new File("foo.test").toPath(), true);
        assertFiltering(filter, new File("FOO.test").toPath(), false); // case-sensitive
        assertFiltering(filter, new File("foo").toPath(), true);
        assertFiltering(filter, new File("bar").toPath(), true);
        assertFiltering(filter, new File("food/").toPath(), true);

        filter = FileFilterUtils.prefixFileFilter("bar");
        assertFiltering(filter, new File("barred\\"), true);
        assertFiltering(filter, new File("test"), false);
        assertFiltering(filter, new File("fo_o.test"), false);
        assertFiltering(filter, new File("abar.exe"), false);
        //
        assertFiltering(filter, new File("barred\\").toPath(), true);
        assertFiltering(filter, new File("test").toPath(), false);
        assertFiltering(filter, new File("fo_o.test").toPath(), false);
        assertFiltering(filter, new File("abar.exe").toPath(), false);

        filter = new PrefixFileFilter("tes");
        assertFiltering(filter, new File("test"), true);
        assertFiltering(filter, new File("fred"), false);
        //
        assertFiltering(filter, new File("test").toPath(), true);
        assertFiltering(filter, new File("fred").toPath(), false);

        assertTrue(filter.accept(testFile.getParentFile(), testFile.getName()));
        assertFalse(filter.accept(fredFile.getParentFile(), fredFile.getName()));
        //
        assertEquals(FileVisitResult.CONTINUE, filter.accept(testPath, null));
        assertEquals(FileVisitResult.TERMINATE, filter.accept(fredPath, null));
        //
        assertTrue(filter.matches(testPath));
        assertFalse(filter.matches(fredPath));

        final List<String> prefixes = Arrays.asList("foo", "fre");
        final IOFileFilter listFilter = new PrefixFileFilter(prefixes);

        assertFalse(listFilter.accept(testFile.getParentFile(), testFile.getName()));
        assertTrue(listFilter.accept(fredFile.getParentFile(), fredFile.getName()));
        //
        assertEquals(FileVisitResult.TERMINATE, listFilter.accept(testPath, null));
        assertEquals(FileVisitResult.CONTINUE, listFilter.accept(fredPath, null));
        //
        assertFalse(listFilter.matches(testPath));
        assertTrue(listFilter.matches(fredPath));

        assertThrows(NullPointerException.class, () -> new PrefixFileFilter((String) null));
        assertThrows(NullPointerException.class, () -> new PrefixFileFilter((String[]) null));
        assertThrows(NullPointerException.class, () -> new PrefixFileFilter((List<String>) null));
    }

    @Test
    void testPrefixCaseInsensitive() throws IOException {

        IOFileFilter filter = new PrefixFileFilter(new String[] {"foo", "bar"}, IOCase.INSENSITIVE);
        assertFiltering(filter, new File("foo.test1"), true);
        assertFiltering(filter, new File("bar.test1"), true);
        assertFiltering(filter, new File("FOO.test1"), true); // case-insensitive
        assertFiltering(filter, new File("BAR.test1"), true); // case-insensitive

        filter = new PrefixFileFilter("bar", IOCase.INSENSITIVE);
        assertFiltering(filter, new File("foo.test2"), false);
        assertFiltering(filter, new File("bar.test2"), true);
        assertFiltering(filter, new File("FOO.test2"), false); // case-insensitive
        assertFiltering(filter, new File("BAR.test2"), true); // case-insensitive

        final List<String> prefixes = Arrays.asList("foo", "bar");
        filter = new PrefixFileFilter(prefixes, IOCase.INSENSITIVE);
        assertFiltering(filter, new File("foo.test3"), true);
        assertFiltering(filter, new File("bar.test3"), true);
        assertFiltering(filter, new File("FOO.test3"), true); // case-insensitive
        assertFiltering(filter, new File("BAR.test3"), true); // case-insensitive

        assertThrows(NullPointerException.class, () -> new PrefixFileFilter((String) null, IOCase.INSENSITIVE));
        assertThrows(NullPointerException.class, () -> new PrefixFileFilter((String[]) null, IOCase.INSENSITIVE));
        assertThrows(NullPointerException.class, () -> new PrefixFileFilter((List<String>) null, IOCase.INSENSITIVE));
        // FileFilterUtils.prefixFileFilter(String, IOCase) tests
        filter = FileFilterUtils.prefixFileFilter("bar", IOCase.INSENSITIVE);
        assertFiltering(filter, new File("foo.test2"), false);
        assertFiltering(filter, new File("bar.test2"), true);
        assertFiltering(filter, new File("FOO.test2"), false); // case-insensitive
        assertFiltering(filter, new File("BAR.test2"), true); // case-insensitive

        assertThrows(NullPointerException.class, () -> FileFilterUtils.prefixFileFilter(null, IOCase.INSENSITIVE));
    }

    @Test
    void testSizeFilterOnFiles() throws Exception {
        final File smallFile = new File(temporaryFolder, "small.txt");
        if (!smallFile.getParentFile().exists()) {
            fail("Cannot create file " + smallFile + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output1 = new BufferedOutputStream(Files.newOutputStream(smallFile.toPath()))) {
            TestUtils.generateTestData(output1, 32);
        }
        final File largeFile = new File(temporaryFolder, "large.txt");
        if (!largeFile.getParentFile().exists()) {
            fail("Cannot create file " + largeFile + " as the parent directory does not exist");
        }
        try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(largeFile.toPath()))) {
            TestUtils.generateTestData(output, 128);
        }
        final IOFileFilter filter1 = FileFilterUtils.sizeFileFilter(64);
        final IOFileFilter filter2 = FileFilterUtils.sizeFileFilter(64, true);
        final IOFileFilter filter3 = FileFilterUtils.sizeFileFilter(64, false);

        assertFiltering(filter1, smallFile, false);
        assertFiltering(filter2, smallFile, false);
        assertFiltering(filter3, smallFile, true);
        assertFiltering(filter1, largeFile, true);
        assertFiltering(filter2, largeFile, true);
        assertFiltering(filter3, largeFile, false);

        // size range tests
        final IOFileFilter filter4 = FileFilterUtils.sizeRangeFileFilter(33, 127);
        final IOFileFilter filter5 = FileFilterUtils.sizeRangeFileFilter(32, 127);
        final IOFileFilter filter6 = FileFilterUtils.sizeRangeFileFilter(33, 128);
        final IOFileFilter filter7 = FileFilterUtils.sizeRangeFileFilter(31, 129);
        final IOFileFilter filter8 = FileFilterUtils.sizeRangeFileFilter(128, 128);

        assertFiltering(filter4, smallFile, false);
        assertFiltering(filter4, largeFile, false);
        assertFiltering(filter5, smallFile, true);
        assertFiltering(filter5, largeFile, false);
        assertFiltering(filter6, smallFile, false);
        assertFiltering(filter6, largeFile, true);
        assertFiltering(filter7, smallFile, true);
        assertFiltering(filter7, largeFile, true);
        assertFiltering(filter8, largeFile, true);

        assertThrows(IllegalArgumentException.class, () -> FileFilterUtils.sizeFileFilter(-1));
    }

    @Test
    void testSizeFilterOnPaths() throws Exception {
        final Path smallFile = Paths.get(temporaryFolder.toString(), "small.txt");
        if (Files.notExists(smallFile.getParent())) {
            fail("Cannot create file " + smallFile + " as the parent directory does not exist");
        }
        try (OutputStream output = Files.newOutputStream(smallFile)) {
            TestUtils.generateTestData(output, 32);
        }
        final Path largeFile = Paths.get(temporaryFolder.toString(), "large.txt");
        if (Files.notExists(largeFile.getParent())) {
            fail("Cannot create file " + largeFile + " as the parent directory does not exist");
        }
        try (OutputStream output = Files.newOutputStream(largeFile)) {
            TestUtils.generateTestData(output, 128);
        }
        final IOFileFilter filter1 = FileFilterUtils.sizeFileFilter(64);
        final IOFileFilter filter2 = FileFilterUtils.sizeFileFilter(64, true);
        final IOFileFilter filter3 = FileFilterUtils.sizeFileFilter(64, false);

        assertFiltering(filter1, smallFile, false);
        assertFiltering(filter2, smallFile, false);
        assertFiltering(filter3, smallFile, true);
        assertFiltering(filter1, largeFile, true);
        assertFiltering(filter2, largeFile, true);
        assertFiltering(filter3, largeFile, false);

        // size range tests
        final IOFileFilter filter4 = FileFilterUtils.sizeRangeFileFilter(33, 127);
        final IOFileFilter filter5 = FileFilterUtils.sizeRangeFileFilter(32, 127);
        final IOFileFilter filter6 = FileFilterUtils.sizeRangeFileFilter(33, 128);
        final IOFileFilter filter7 = FileFilterUtils.sizeRangeFileFilter(31, 129);
        final IOFileFilter filter8 = FileFilterUtils.sizeRangeFileFilter(128, 128);

        assertFiltering(filter4, smallFile, false);
        assertFiltering(filter4, largeFile, false);
        assertFiltering(filter5, smallFile, true);
        assertFiltering(filter5, largeFile, false);
        assertFiltering(filter6, smallFile, false);
        assertFiltering(filter6, largeFile, true);
        assertFiltering(filter7, smallFile, true);
        assertFiltering(filter7, largeFile, true);
        assertFiltering(filter8, largeFile, true);

        assertThrows(IllegalArgumentException.class, () -> FileFilterUtils.sizeFileFilter(-1));
    }

    @Test
    void testSuffix() throws IOException {
        IOFileFilter filter = new SuffixFileFilter("tes", "est");
        final File testFile = new File("test");
        final Path testPath = testFile.toPath();
        final File fredFile = new File("fred");
        final Path fredPath = fredFile.toPath();
        //
        assertFiltering(filter, new File("fred.tes"), true);
        assertFiltering(filter, new File("fred.est"), true);
        assertFiltering(filter, new File("fred.EST"), false); // case-sensitive
        assertFiltering(filter, new File("fred.exe"), false);
        //
        assertFiltering(filter, new File("fred.tes").toPath(), true);
        assertFiltering(filter, new File("fred.est").toPath(), true);
        assertFiltering(filter, new File("fred.EST").toPath(), false); // case-sensitive
        assertFiltering(filter, new File("fred.exe").toPath(), false);

        filter = FileFilterUtils.or(FileFilterUtils.suffixFileFilter("tes"), FileFilterUtils.suffixFileFilter("est"));
        assertFiltering(filter, new File("fred"), false);
        assertFiltering(filter, new File(".tes"), true);
        assertFiltering(filter, new File("fred.test"), true);
        //
        assertFiltering(filter, new File("fred").toPath(), false);
        assertFiltering(filter, new File(".tes").toPath(), true);
        assertFiltering(filter, new File("fred.test").toPath(), true);

        filter = new SuffixFileFilter("est");
        assertFiltering(filter, new File("test"), true);
        assertFiltering(filter, new File("fred"), false);
        //
        assertFiltering(filter, new File("test").toPath(), true);
        assertFiltering(filter, new File("fred").toPath(), false);

        assertTrue(filter.accept(testFile.getParentFile(), testFile.getName()));
        assertFalse(filter.accept(fredFile.getParentFile(), fredFile.getName()));
        //
        assertEquals(FileVisitResult.CONTINUE, filter.accept(testPath, null));
        assertEquals(FileVisitResult.TERMINATE, filter.accept(fredPath, null));
        //
        assertTrue(filter.matches(testPath));
        assertFalse(filter.matches(fredPath));

        final List<String> prefixes = Arrays.asList("ood", "red");
        final IOFileFilter listFilter = new SuffixFileFilter(prefixes);

        assertFalse(listFilter.accept(testFile.getParentFile(), testFile.getName()));
        assertTrue(listFilter.accept(fredFile.getParentFile(), fredFile.getName()));
        //
        assertEquals(FileVisitResult.TERMINATE, listFilter.accept(testPath, null));
        assertEquals(FileVisitResult.CONTINUE, listFilter.accept(fredPath, null));

        assertThrows(NullPointerException.class, () -> new SuffixFileFilter((String) null));
        assertThrows(NullPointerException.class, () -> new SuffixFileFilter((String[]) null));
        assertThrows(NullPointerException.class, () -> new SuffixFileFilter((List<String>) null));
    }

    @Test
    void testSuffixCaseInsensitive() throws IOException {

        IOFileFilter filter = new SuffixFileFilter(new String[] {"tes", "est"}, IOCase.INSENSITIVE);
        assertFiltering(filter, new File("foo.tes"), true);
        assertFiltering(filter, new File("foo.est"), true);
        assertFiltering(filter, new File("foo.EST"), true); // case-sensitive
        assertFiltering(filter, new File("foo.TES"), true); // case-sensitive
        assertFiltering(filter, new File("foo.exe"), false);

        filter = new SuffixFileFilter("est", IOCase.INSENSITIVE);
        assertFiltering(filter, new File("test"), true);
        assertFiltering(filter, new File("TEST"), true);

        final List<String> suffixes = Arrays.asList("tes", "est");
        filter = new SuffixFileFilter(suffixes, IOCase.INSENSITIVE);
        assertFiltering(filter, new File("bar.tes"), true);
        assertFiltering(filter, new File("bar.est"), true);
        assertFiltering(filter, new File("bar.EST"), true); // case-sensitive
        assertFiltering(filter, new File("bar.TES"), true); // case-sensitive
        assertFiltering(filter, new File("bar.exe"), false);

        assertThrows(NullPointerException.class, () -> new SuffixFileFilter((String) null, IOCase.INSENSITIVE));
        assertThrows(NullPointerException.class, () -> new SuffixFileFilter((String[]) null, IOCase.INSENSITIVE));
        assertThrows(NullPointerException.class, () -> new SuffixFileFilter((List<String>) null, IOCase.INSENSITIVE));

        // FileFilterUtils.suffixFileFilter(String, IOCase) tests
        filter = FileFilterUtils.suffixFileFilter("est", IOCase.INSENSITIVE);
        assertFiltering(filter, new File("test"), true);
        assertFiltering(filter, new File("TEST"), true);

        assertThrows(NullPointerException.class, () -> FileFilterUtils.suffixFileFilter(null, IOCase.INSENSITIVE));
    }

    @Test
    void testTrue() throws IOException {
        final IOFileFilter filter = FileFilterUtils.trueFileFilter();
        assertFiltering(filter, new File("foo.test"), true);
        assertFiltering(filter, new File("foo"), true);
        assertFiltering(filter, (File) null, true);
        //
        assertFiltering(filter, new File("foo.test").toPath(), true);
        assertFiltering(filter, new File("foo").toPath(), true);
        assertFiltering(filter, (Path) null, true);
        //
        assertSame(TrueFileFilter.TRUE, TrueFileFilter.INSTANCE);
        assertSame(FalseFileFilter.FALSE, TrueFileFilter.INSTANCE.negate());
        assertSame(FalseFileFilter.INSTANCE, TrueFileFilter.INSTANCE.negate());
        assertNotNull(TrueFileFilter.INSTANCE.toString());
    }

}
