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