001package Torello.HTML;
002
003import Torello.Java.StrFilter;
004import Torello.Java.StrCmpr;
005
006import java.util.function.Predicate;
007import java.util.Iterator;
008import java.net.URL;
009
010/**
011 * A simple lambda-target which extends {@code Predicate<URL>}.
012 * 
013 * <EMBED CLASS='external-html' DATA-FILE-ID=URL_FILTER>
014 * 
015 * @see StrFilter
016 */
017@FunctionalInterface
018public interface URLFilter extends Predicate<URL>, java.io.Serializable
019{
020    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUIDFI>  */
021    public static final long serialVersionUID = 1;
022
023
024    // ********************************************************************************************
025    // ********************************************************************************************
026    // Functional-Interface Method
027    // ********************************************************************************************
028    // ********************************************************************************************
029
030
031    /**
032     * <EMBED CLASS='external-html' DATA-FILE-ID=FUNC_INTER_METH>
033     * 
034     * <BR /><BR />This method will receive a {@code URL.}  The purpose of this method is to 
035     * provide an easy means to filter certain {@code URL's} from a {@code URL}-generating list.
036     *
037     * <BR /><BR /><B><SPAN STYLE="color: red;">PRECISE NOTE:</B></SPAN> This method should return
038     * {@code FALSE} if the passed {@code URL} <I><B>should be skipped</B></I>.  A return value of
039     * {@code TRUE} implies that the {@code URL} is not to be ignored or passed over, but rather
040     * 'kept.'
041     *
042     * <BR /><BR /><B>NOTE:</B> This behavior is compatible with the Java Stream's method
043     * {@code "filter(Predicate<...>)".}
044     * 
045     * @param url This is a {@code URL} that will be checked against the constraints specified by
046     * {@code 'this'} filter.
047     * 
048     * @return When implementing this method, returning {@code TRUE} must mean that the {@code URL}
049     * has passed the filter's test-requirements (and will subsequently be retained by whatever
050     * code is carrying out the filter operation).
051     */
052    public boolean test(URL url);
053
054
055    // ********************************************************************************************
056    // ********************************************************************************************
057    // The basic-required methods, in order to make this a "Respectable Filter Class"
058    // ********************************************************************************************
059    // ********************************************************************************************
060
061
062    /*
063     * This is the standard-java {@code Predicate} method {@code 'and'}.  If a user wants to apply
064     * two {@code URLFilters,} this method will use a lambda-expression to create a new
065     * {@code URLFilter} that "logically-AND's" the {@code 'other' Predicate} with {@code 'this'
066     * Predicate.}
067     * 
068     * @param other Some other {@code URLFilter} - one that does some other test.
069     * 
070     * @return A new Java-{@code Predicate} that performs both tests ({@code 'this'} and
071     * {@code 'other'}), and returns the logical-AND.
072     *
073    default URLFilter and(URLFilter other)
074    {
075        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once
076        // the lambda-predicate is invoked.
077        if (other == null) throw new NullPointerException
078            ("The parameter 'other' to URLFilter.and(other) was null.");
079
080        return (URL url) -> this.test(url) && other.test(url);
081    }
082
083    /*
084     * This is the standard-java {@code Predicate} method {@code 'or'}.  If a user wants to apply
085     * two {@code URLFilters,} this method will use a lambda-expression to create a new
086     * {@code URLFilter} that "logically-OR's" the {@code 'other' Predicate} with {@code 'this'
087     * Predicate.}
088     * 
089     * @param other Some other {@code URLFilter} - one that does some other test.
090     * 
091     * @return A new Java-{@code Predicate} that performs both tests ({@code 'this'} and 
092     * {@code 'other'}), and returns the logical-OR.
093     *
094    default URLFilter or(URLFilter other)
095    {
096        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once
097        // the lambda-predicate is invoked.
098        if (other == null) throw new NullPointerException
099            ("The parameter 'other' to URLFilter.or(other) was null.");
100
101        return (URL url) -> this.test(url) || other.test(url);
102    }
103
104
105    /*
106     * This is the standard-java predicate method {@code 'not'}.  This method will use a
107     * lambda-expression to create a new {@code URLFilter} that is the "logical-NOT" of the
108     * original test (a.k.a. {@code 'this' Predicate}).
109     * 
110     * @return A new Java-{@code Predicate} that performs negates the results of {@code 'this'} 
111     * {@code Predicate.}
112     *
113    default URLFilter negate() { return (URL url) -> ! this.test(url); }
114    */
115
116    /**
117     * This {@code URLFilter} will KEEP any Image {@code URL's} whose name ends with the standard
118     * image filenames.
119     *
120     * <BR /><BR /><B>WARNING:</B> There are occasions where an Image-{@code URL} is "handled" by 
121     * a web-server internally, and the actual {@code URL} itself does not look like an image
122     * file-name at all.  This has the inconvenient implication for this (factory-generated)
123     * {@code Predicate} that it might return erroneous results.  An actual image file that does
124     * not end with {@code '.jpg'} or {@code '.bmp'} could be rejected, and a {@code URL} that
125     * happens to end with these {@code String's} but is not an image, might also be kept.
126     * 
127     * @see StrCmpr#endsWithXOR_CI(String, String[])
128     */
129    public static final URLFilter imagesKEEP = (URL url) ->
130    {
131        return StrCmpr.endsWithXOR_CI
132            (url.toString().trim(), ".jpg", ".jpeg", ".gif", ".png", ".bmp");
133    };
134
135    /**
136     * This {@code URLFilter} will REJECT any Image {@code URL's} whose name ends with the
137     * standard image filenames.
138     *
139     * <BR /><BR /><B>WARNING:</B> There are occasions where an Image-{@code URL} is "handled" by
140     * a web-server internally, and the actual {@code URL} itself does not look like an image
141     * file-name at all.  This has the inconvenient implication for this (factory-generated)
142     * {@code Predicate} that it might return erroneous results.  An actual image file that does
143     * not end with {@code '.jpg'} or {@code '.bmp'} could be kept, and a {@code URL} that happens
144     * to end with these {@code String's} but is not an image, could be rejected.
145     * 
146     * @see StrCmpr#endsWithNAND_CI(String, String[])
147     */
148    public static final URLFilter imagesREJECT = (URL url) ->
149    {
150        return StrCmpr.endsWithNAND_CI
151            (url.toString().trim(), ".jpg", ".jpeg", ".gif", ".png", ".bmp");
152    };
153
154    /**
155     * This is similar to the java streams function {@code filter(Predicate<>)}.  Elements that do 
156     * not meet the criteria specified by this (factory-generated) {@code URLFilter} -
157     * <I>specifically, if an element of the input-parameter {@code 'urlList'} would evaluate to
158     * {@code FALSE}</I> - then that element shall be removed from the list.
159     * 
160     * @param urls An {@code Iterable} of {@code URL's} which the user would like filtered using 
161     * {@code 'this'} filter.
162     * 
163     * @return The number of elements that were removed from parameter {@code 'urls'} based on the
164     * results of the {@code URLFilter.test()} of {@code 'this'} instance.
165     */
166    public default int filter(Iterable<URL> urls)
167    {
168        int             removeCount = 0;
169        Iterator<URL>   iter        = urls.iterator();
170
171        // If the filter test returns FALSE, then remove the URL from the collection.
172        // Increment the removeCount Counter.
173
174        while (iter.hasNext()) if (! test(iter.next())) { removeCount++; iter.remove(); }
175
176        return removeCount;
177    }
178
179    /**
180     * This wraps a {@code StrFilter} inside of a {@code URLFilter}.  The {@code String}-comparison
181     * that is performed will use the full-path-name of the {@code URL}.
182     * 
183     * <BR /><BR /><B><SPAN STYLE="color: red;">StrFilter NOTE:</SPAN></B> The class
184     * {@code 'StrFilter'} can be used in conjunction with the class-specific filters, for
185     * instance, this class {@code 'URLFilter'}
186     * 
187     * @param sf This is a {@code String Predicate} that has (usually, but not required) been built
188     * by one of the many {@code String}-Filter Factory-Build static-methods of
189     * {@code class StrFilter.} The {@code Predicate's} that are constructed via the build methods
190     * of {@code StrFilter} call the standard method {@code java.lang.Object.toString()} on the
191     * objects they receive for testing.
192     * 
193     * @return FileNodeFilter This will return an instance of a {@code URLFilter} that will test
194     * the {@code url} as a {@code String.}
195     * 
196     * @see StrFilter
197     */ 
198    public static URLFilter fromStrFilter(StrFilter sf)
199    {
200        if (sf == null) throw new NullPointerException(
201            "The String-Filter Predicate Parameter 'sf' in static-factory builder method " +
202            "'fromStrFilter' was passed a null value."
203        );
204
205        return (URL url) -> sf.test(url);
206    }
207}