001package Torello.Java;
002
003import Torello.Java.StrFilter;
004
005import java.util.function.Predicate;
006import java.util.Iterator;
007
008/**
009 * A simple functional-interface (lambda) for filtering lists of <CODE>FileNode</CODE>.
010 * 
011 * <EMBED CLASS='external-html' DATA-FILE-ID=FILENODE_FILTER>
012 * 
013 * @see StrFilter
014 */
015@FunctionalInterface
016public interface FileNodeFilter extends Predicate<FileNode>, java.io.Serializable
017{
018    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUIDFI>  */
019    public static final long serialVersionUID = 1;
020
021    /**
022     * <EMBED CLASS='external-html' DATA-FILE-ID=FUNC_INTER_METH>
023     *
024     * @param fn Any file node
025     *
026     * @return A {@code TRUE} value should mean that the {@code FileNode} has passed the test
027     * (will be kept / retained).  A return value of {@code FALSE} should indicate that the
028     * {@code FileNode} needs to be filtered.
029     */
030    public boolean test(FileNode fn);
031
032    /**
033     * Standard Java-{@code Predicate}, Java Functional-Interface AND routine.
034     *
035     * <BR /><BR />Logically AND's <B>{@code 'this'}</B> {@code FileNodeFilter} with the filter
036     * parameter <B>{@code 'other'}</B>
037     *
038     * @param other A 2nd {@code FileNodeFilter} with which <B>{@code 'this'}</B> filter may be
039     * AND'ed.
040     *
041     * @return A new {@code FileNodeFilter} that is the logical-and of <B>{@code 'this'}</B> filter
042     * and <B>{@code 'other'}</B>
043     */
044    default FileNodeFilter and(FileNodeFilter other)
045    {
046        // FAIL-FAST: Check that the lambda will not throw a null pointer exception prior to
047        // creating the lambda.
048
049        if (other == null) throw new NullPointerException
050            ("The parameter 'other' to FileNodeFilter.and(other) was null.");
051
052        return (FileNode f) -> this.test(f) && other.test(f);
053    }
054
055    /**
056     * Standard Java-{@code Predicate}, Java Functional-Interface OR routine.
057     *
058     * <BR /><BR />Logically OR's <B>{@code 'this'}</B> {@code FileNodeFilter} with the filter
059     * parameter <B>{@code 'other'}</B>
060     *
061     * @param other A 2nd {@code FileNodeFilter} with which <B>{@code 'this'}</B> filter may be
062     * or'ed.
063     *
064     * @return A new FileNodeFilter that is the logical-or of <B>{@code 'this'}</B> filter and
065     * <B>"other"</B>
066     */
067    default FileNodeFilter or(FileNodeFilter other)
068    {
069        // FAIL-FAST: Check that the lambda will not throw a null pointer exception prior to
070        // creating the lambda.
071
072        if (other == null) throw new NullPointerException
073            ("The parameter 'other' to FileNodeFilter.or(other) was null..");
074
075        return (FileNode f) -> this.test(f) || other.test(f);
076    }
077
078    /**
079     * Standard Java-{@code Predicate}, Java Functional-Interface NEGATE routine.
080     *
081     * <BR /><BR />Generates 's a {@code FileNodeFilter} that is the "logical-not" of
082     * <B>{@code 'this'}</B> filter.
083     *
084     * @return A new {@code FileNodeFilter} that would return {@code TRUE} whenever
085     * <B>{@code 'this'}</B> would return {@code FALSE}, and vice-versa.
086     */
087    default FileNodeFilter negate() { return (FileNode f) -> ! this.test(f); }
088
089    /**
090     * This is similar to the java streams function {@code filter(Predicate<>)}.  Elements that do
091     * not meet the criteria specified by this {@code FileNodeFilter} - <I>specifically, if an
092     * element of the input-parameter {@code 'urlList'} would evaluate to {@code FALSE}</I> - then
093     * that element shall be removed from the list.
094     *
095     * @param fileNodes An {@code Iterable} of {@code FileNode's} which the user would like
096     * filtered using {@code 'this'} filter.
097     *
098     * @return The number of elements that were removed from parameter {@code 'fileNodes'} based on
099     * the results of the {@code 'test(FileNode)'} lambda-method {@code FileNodeFilter.test()} of
100     * {@code 'this'} instance.
101     */
102    public default int filter(Iterable<FileNode> fileNodes)
103    {
104        int                 removeCount = 0;
105        Iterator<FileNode>  iter        = fileNodes.iterator();
106
107        // If the filter test returns FALSE, then remove the URL from the collection.
108        // Increment the removeCount Counter.
109
110        while (iter.hasNext()) if (! test(iter.next())) { removeCount++; iter.remove(); }
111
112        return removeCount;
113    }
114
115    /**
116     * This wraps a {@code StrFilter} inside of a {@code FileNodeFilter}.  The
117     * {@code String}-comparison that is performed will use the full-path-name of the
118     * {@code FileNode}.
119     *
120     * @param sf This is a "String Predicate" that has (usually, but not required) been built by
121     * one of the many {@code String}-Filter Factory-Build static-methods of
122     * {@code class StrFilter}.  The Predicate's that are constructed via the build methods of
123     * {@code StrFilter} call the {@code Object.toString()} method on the objects they receive for
124     * testing.
125     *
126     * @return FileNodeFilter This will return an instance of a {@code FileNodeFilter} that will
127     * test the full-path-name as a {@code String}.
128     *
129     * @see StrFilter
130     */ 
131    public static FileNodeFilter fromStrFilter(StrFilter sf)
132    {
133        if (sf == null) throw new NullPointerException(
134            "The String-Filter Predicate Parameter 'sf' in static-factory builder method " +
135            "'fromStrFilter' was passed a null value."
136        );
137
138        return (FileNode fn) -> sf.test(fn);
139    }
140}