1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package Torello.HTML;

import Torello.Java.StrFilter;
import Torello.Java.StrCmpr;

import java.util.function.Predicate;
import java.util.Iterator;
import java.net.URL;

/**
 * A simple lambda-target which extends {@code Predicate<URL>}.
 * 
 * <EMBED CLASS='external-html' DATA-FILE-ID=URL_FILTER>
 * 
 * @see StrFilter
 */
@FunctionalInterface
public interface URLFilter extends Predicate<URL>, java.io.Serializable
{
    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUIDFI>  */
    public static final long serialVersionUID = 1;


    // ********************************************************************************************
    // ********************************************************************************************
    // Functional-Interface Method
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=FUNC_INTER_METH>
     * 
     * <BR /><BR />This method will receive a {@code URL.}  The purpose of this method is to 
     * provide an easy means to filter certain {@code URL's} from a {@code URL}-generating list.
     *
     * <BR /><BR /><B><SPAN STYLE="color: red;">PRECISE NOTE:</B></SPAN> This method should return
     * {@code FALSE} if the passed {@code URL} <I><B>should be skipped</B></I>.  A return value of
     * {@code TRUE} implies that the {@code URL} is not to be ignored or passed over, but rather
     * 'kept.'
     *
     * <BR /><BR /><B>NOTE:</B> This behavior is compatible with the Java Stream's method
     * {@code "filter(Predicate<...>)".}
     * 
     * @param url This is a {@code URL} that will be checked against the constraints specified by
     * {@code 'this'} filter.
     * 
     * @return When implementing this method, returning {@code TRUE} must mean that the {@code URL}
     * has passed the filter's test-requirements (and will subsequently be retained by whatever
     * code is carrying out the filter operation).
     */
    public boolean test(URL url);


    // ********************************************************************************************
    // ********************************************************************************************
    // The basic-required methods, in order to make this a "Respectable Filter Class"
    // ********************************************************************************************
    // ********************************************************************************************


    /*
     * This is the standard-java {@code Predicate} method {@code 'and'}.  If a user wants to apply
     * two {@code URLFilters,} this method will use a lambda-expression to create a new
     * {@code URLFilter} that "logically-AND's" the {@code 'other' Predicate} with {@code 'this'
     * Predicate.}
     * 
     * @param other Some other {@code URLFilter} - one that does some other test.
     * 
     * @return A new Java-{@code Predicate} that performs both tests ({@code 'this'} and
     * {@code 'other'}), and returns the logical-AND.
     *
    default URLFilter and(URLFilter other)
    {
        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once
        // the lambda-predicate is invoked.
        if (other == null) throw new NullPointerException
            ("The parameter 'other' to URLFilter.and(other) was null.");

        return (URL url) -> this.test(url) && other.test(url);
    }

    /*
     * This is the standard-java {@code Predicate} method {@code 'or'}.  If a user wants to apply
     * two {@code URLFilters,} this method will use a lambda-expression to create a new
     * {@code URLFilter} that "logically-OR's" the {@code 'other' Predicate} with {@code 'this'
     * Predicate.}
     * 
     * @param other Some other {@code URLFilter} - one that does some other test.
     * 
     * @return A new Java-{@code Predicate} that performs both tests ({@code 'this'} and 
     * {@code 'other'}), and returns the logical-OR.
     *
    default URLFilter or(URLFilter other)
    {
        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once
        // the lambda-predicate is invoked.
        if (other == null) throw new NullPointerException
            ("The parameter 'other' to URLFilter.or(other) was null.");

        return (URL url) -> this.test(url) || other.test(url);
    }


    /*
     * This is the standard-java predicate method {@code 'not'}.  This method will use a
     * lambda-expression to create a new {@code URLFilter} that is the "logical-NOT" of the
     * original test (a.k.a. {@code 'this' Predicate}).
     * 
     * @return A new Java-{@code Predicate} that performs negates the results of {@code 'this'} 
     * {@code Predicate.}
     *
    default URLFilter negate() { return (URL url) -> ! this.test(url); }
    */

    /**
     * This {@code URLFilter} will KEEP any Image {@code URL's} whose name ends with the standard
     * image filenames.
     *
     * <BR /><BR /><B>WARNING:</B> There are occasions where an Image-{@code URL} is "handled" by 
     * a web-server internally, and the actual {@code URL} itself does not look like an image
     * file-name at all.  This has the inconvenient implication for this (factory-generated)
     * {@code Predicate} that it might return erroneous results.  An actual image file that does
     * not end with {@code '.jpg'} or {@code '.bmp'} could be rejected, and a {@code URL} that
     * happens to end with these {@code String's} but is not an image, might also be kept.
     * 
     * @see StrCmpr#endsWithXOR_CI(String, String[])
     */
    public static final URLFilter imagesKEEP = (URL url) ->
    {
        return StrCmpr.endsWithXOR_CI
            (url.toString().trim(), ".jpg", ".jpeg", ".gif", ".png", ".bmp");
    };

    /**
     * This {@code URLFilter} will REJECT any Image {@code URL's} whose name ends with the
     * standard image filenames.
     *
     * <BR /><BR /><B>WARNING:</B> There are occasions where an Image-{@code URL} is "handled" by
     * a web-server internally, and the actual {@code URL} itself does not look like an image
     * file-name at all.  This has the inconvenient implication for this (factory-generated)
     * {@code Predicate} that it might return erroneous results.  An actual image file that does
     * not end with {@code '.jpg'} or {@code '.bmp'} could be kept, and a {@code URL} that happens
     * to end with these {@code String's} but is not an image, could be rejected.
     * 
     * @see StrCmpr#endsWithNAND_CI(String, String[])
     */
    public static final URLFilter imagesREJECT = (URL url) ->
    {
        return StrCmpr.endsWithNAND_CI
            (url.toString().trim(), ".jpg", ".jpeg", ".gif", ".png", ".bmp");
    };

    /**
     * This is similar to the java streams function {@code filter(Predicate<>)}.  Elements that do 
     * not meet the criteria specified by this (factory-generated) {@code URLFilter} -
     * <I>specifically, if an element of the input-parameter {@code 'urlList'} would evaluate to
     * {@code FALSE}</I> - then that element shall be removed from the list.
     * 
     * @param urls An {@code Iterable} of {@code URL's} which the user would like filtered using 
     * {@code 'this'} filter.
     * 
     * @return The number of elements that were removed from parameter {@code 'urls'} based on the
     * results of the {@code URLFilter.test()} of {@code 'this'} instance.
     */
    public default int filter(Iterable<URL> urls)
    {
        int             removeCount = 0;
        Iterator<URL>   iter        = urls.iterator();

        // If the filter test returns FALSE, then remove the URL from the collection.
        // Increment the removeCount Counter.

        while (iter.hasNext()) if (! test(iter.next())) { removeCount++; iter.remove(); }

        return removeCount;
    }

    /**
     * This wraps a {@code StrFilter} inside of a {@code URLFilter}.  The {@code String}-comparison
     * that is performed will use the full-path-name of the {@code URL}.
     * 
     * <BR /><BR /><B><SPAN STYLE="color: red;">StrFilter NOTE:</SPAN></B> The class
     * {@code 'StrFilter'} can be used in conjunction with the class-specific filters, for
     * instance, this class {@code 'URLFilter'}
     * 
     * @param sf This is a {@code String Predicate} that has (usually, but not required) been built
     * by one of the many {@code String}-Filter Factory-Build static-methods of
     * {@code class StrFilter.} The {@code Predicate's} that are constructed via the build methods
     * of {@code StrFilter} call the standard method {@code java.lang.Object.toString()} on the
     * objects they receive for testing.
     * 
     * @return FileNodeFilter This will return an instance of a {@code URLFilter} that will test
     * the {@code url} as a {@code String.}
     * 
     * @see StrFilter
     */ 
    public static URLFilter fromStrFilter(StrFilter sf)
    {
        if (sf == null) throw new NullPointerException(
            "The String-Filter Predicate Parameter 'sf' in static-factory builder method " +
            "'fromStrFilter' was passed a null value."
        );

        return (URL url) -> sf.test(url);
    }
}