001package Torello.HTML.NodeSearch;
002
003import java.util.*;
004
005import java.util.function.Predicate;
006
007import Torello.HTML.*;
008
009import Torello.Java.LV;
010
011/**
012 * A Java Generic {@code Iterator}-Class that for iterating {@link TagNode}, {@link TextNode} and
013 * {@code CommentNode} instances which match user-provided search-criteria.
014 * 
015 * <EMBED CLASS='external-html' DATA-FILE-ID=HNLI_EXTENDS_LITER>
016 * <EMBED CLASS='external-html' DATA-FILE-ID=HNLI_EASY_TO_USE>
017 * 
018 * @param <E> The type of {@code HTMLNode} being iterated.
019 */
020@SuppressWarnings("unchecked")
021@Torello.JavaDoc.JDHeaderBackgroundImg
022public class HNLI<E extends HTMLNode> extends AbstractHNLI<E, E>
023{
024    // ********************************************************************************************
025    // ********************************************************************************************
026    // Private Fields
027    // ********************************************************************************************
028    // ********************************************************************************************
029
030
031    // -1 means a "Right-Direction Match" has not been found/identified yet.
032    private int hasNextVectorPos = -1;
033
034    // -1 means a "Left-Direction Match" has not been found/identified yet.
035    private int hasPrevVectorPos = -1;
036
037
038    // ********************************************************************************************
039    // ********************************************************************************************
040    // Only Constructor **AND** Package-Private Abstract-Method Implementations
041    // ********************************************************************************************
042    // ********************************************************************************************
043
044
045    /**
046     * This will produce an {@code Iterator<E>}.  The last parameter to this constructor
047     * {@code Class<E> c} is required since, as per Java's Erasure "Feature," there is no way
048     * to identify what the Variable-Type Parameter {@code 'E'} evaluates at Run-Time.
049     *
050     * @param html This may be any HTML {@code Vector} or sub-section.
051     *
052     * @param p This is a {@code java.util.function.Predicate} that identifies when the 
053     * {@code Iterator} should consider an instance of {@code 'E'} to be a "Match."
054     *
055     * @param c This parameter should just be the value of a call to {@code 'E'.getClass()} where
056     * {@code 'E'} is the instance of the variable-type parameter {@code 'E'} used in this method.
057     */
058    HNLI (Vector<? extends HTMLNode> html, Predicate<E> p, Class<E> c)
059    { super(html, p, c); }
060
061    void RESET_MATCHES() { hasNextVectorPos = hasPrevVectorPos = -1; }
062
063    int REMOVE() { v.remove(cursor); return 1; }
064
065
066    // ********************************************************************************************
067    // ********************************************************************************************
068    // "Previous" - Retrieval Operations
069    // ********************************************************************************************
070    // ********************************************************************************************
071
072
073    /**
074     * Use this method to find out whether the underlying {@code Vector} and current {@code cursor}
075     * position would retrieve another match if {@code 'previous()'} or {@code 'previousIndex()'}
076     * were called.
077     *
078     * @return This shall return {@code TRUE} if calling the {@code previous()}, or
079     * {@code previousIndex()} methods would return another node-match.  This method shall return
080     * {@code FALSE} if calling {@code previous} would generate / throw a
081     * {@code 'NoSuchElementException'} - <I>because there are no more matches in the underlying
082     * {@code Vector}, given the current {@code cursor} position.</I>
083     *
084     * @throws ConcurrentModificationException
085     * <EMBED CLASS='external-html' DATA-FILE-ID=CONC_MOD_EX>
086     */
087    public boolean hasPrevious()
088    {
089        CHECK_CME();
090
091        if (hasPrevVectorPos != -1) return true;
092
093        Object o; // Temp Object
094
095        int LOOP_BOUNDARY = (minCursor == -1) ? 0 : minCursor;
096
097        if (cursor == -1) cursor = LOOP_BOUNDARY;  // will return false
098
099        // System.out.println("Loop Boundary: " + LOOP_BOUNDARY + ", cursor: " + cursor);
100
101        while (--cursor >= LOOP_BOUNDARY)
102
103            if (c.isInstance(o = v.elementAt(cursor)) && p.test(c.cast(o)))
104            {
105                hasPrevVectorPos = cursor;
106                return true;
107            }
108
109        return false;
110    }
111
112    /**
113     * Returns the nearest node-match in the underlying {@code Vector}, given the current
114     * {@code cursor} position - <I>when searching in the left-direction, or in the direction of
115     * decreasing {@code Vector}-indices.</I>
116     *
117     * @return This shall return the node-match that is directly previous to the current
118     * {@code cursor} position.
119     *
120     * @throws ConcurrentModificationException
121     * <EMBED CLASS='external-html' DATA-FILE-ID=CONC_MOD_EX>
122     *
123     * @throws NoSuchElementException If there aren't any more matches available, this exception
124     * shall throw.  Avoid having to catch this exception by always calling method
125     * {@code 'hasPrevious'}, and only invoking {@code 'previous'} if that method returned
126     * {@code TRUE}.
127     */
128    public E previous()
129    { return (E) v.elementAt(previousIndex()); }
130
131    /**
132     * Returns the nearest node-match, <I><B>as an integer {@code Vector}-index</I></B>, in the
133     * underlying {@code Vector}, given the current {@code cursor} position - <I>when searching in
134     * the left-direction, or in the direction of decreasing {@code Vector}-indices.</I>
135     *
136     * @return This shall return the node-match that is directly previous to the current
137     * {@code cursor} position.
138     *
139     * @throws ConcurrentModificationException
140     * <EMBED CLASS='external-html' DATA-FILE-ID=CONC_MOD_EX>
141     *
142     * @throws NoSuchElementException If there aren't any more matches available, this exception
143     * shall throw.  Avoid having to catch this exception by always calling method
144     * {@code 'hasPrevious()'}, and only invoking {@code 'previousIndex()'} if that method
145     * returned <B>TRUE.</B>
146     */
147    public int previousIndex()
148    {
149        CHECK_CME();
150
151        int temp = hasPrevVectorPos;
152
153        hasPrevVectorPos = hasNextVectorPos = -1;
154        modifiedSince = false;
155
156        if (temp != -1) return temp;
157
158        Object o; // Temp Object
159
160        int LOOP_BOUNDARY = (minCursor == -1) ? 0 : minCursor;
161
162        if (cursor == -1) cursor = LOOP_BOUNDARY; // will throw exception
163
164        while (--cursor >= LOOP_BOUNDARY)
165
166            if (c.isInstance(o = v.elementAt(cursor)) && p.test(c.cast(o)))
167                return cursor;
168
169        throw new NoSuchElementException("There are no more 'previous' elements available.");
170    }
171
172
173    // ********************************************************************************************
174    // ********************************************************************************************
175    // "Next" - Retrieval Operations
176    // ********************************************************************************************
177    // ********************************************************************************************
178
179
180    /**
181     * Use this method to find out whether the underlying {@code Vector} and current {@code cursor}
182     * position would retrieve another match if {@code 'next()'} or {@code 'nextIndex()'} were
183     * called.
184     *
185     * @return This shall return {@code TRUE} if calling the {@code next()}, or {@code nextIndex()}
186     * methods would return another node-match.  This method shall return {@code FALSE} if calling
187     * {@code next()} would generate / throw a {@code 'NoSuchElementException'} - <I>because there
188     * are no more matches in the underlying {@code Vector}, given the current {@code cursor}
189     * position.</I>
190     *
191     * @throws ConcurrentModificationException
192     * <EMBED CLASS='external-html' DATA-FILE-ID=CONC_MOD_EX>
193     */
194    public boolean hasNext()
195    {
196        CHECK_CME();
197
198        if (hasNextVectorPos != -1) return true;
199
200        Object o; // Temp Object
201
202        int LOOP_BOUNDARY = (maxCursor == -1) ? (v.size() - 1) : maxCursor;
203
204        if (cursor == -1) cursor = (minCursor == -1) ? -1 : (minCursor-1);
205
206        // System.out.println("Loop Boundary: " + LOOP_BOUNDARY + ", cursor: " + cursor);
207
208        while (++cursor <= LOOP_BOUNDARY)
209
210            if (c.isInstance(o = v.elementAt(cursor)) && p.test(c.cast(o)))
211                { hasNextVectorPos=cursor;  return true; }
212
213        return false;
214    }
215
216    /**
217     * Returns the nearest node-match in the underlying {@code Vector}, given the current 
218     * {@code cursor} position - <I>when searching in the right-direction, or in the direction of
219     * increasing {@code Vector}-indices.</I>
220     *
221     * @return This shall return the node-match that is directly next to the current 
222     * {@code cursor} position.
223     *
224     * @throws ConcurrentModificationException
225     * <EMBED CLASS='external-html' DATA-FILE-ID=CONC_MOD_EX>
226     *
227     * @throws NoSuchElementException If there aren't any more matches available, this exception
228     * shall throw.  Avoid having to catch this exception by always calling method
229     * {@code 'hasNext()'}, and only invoking {@code 'next()'} if that method returned {@code TRUE}.
230     */
231    public E next()
232    { return (E) v.elementAt(nextIndex()); }
233
234    /**
235     * Returns the nearest node-match, <I><B>as an integer {@code Vector}-index</I></B>, in the
236     * underlying {@code Vector}, given the current {@code cursor} position - <I>when searching in
237     * the right-direction, or in the direction of increasing {@code Vector}-indices.</I>
238     *
239     * @return This shall return the node-match that is directly next to the current {@code cursor}
240     * position.
241     *
242     * @throws ConcurrentModificationException
243     * <EMBED CLASS='external-html' DATA-FILE-ID=CONC_MOD_EX>
244     *
245     * @throws NoSuchElementException If there aren't any more matches available, this exception
246     * shall throw.  Avoid having to catch this exception by always calling method
247     * {@code 'hasNext()'}, and  only invoking {@code 'nextIndex()'} if that method returned
248     * <B>TRUE.</B>
249     */
250    public int nextIndex()
251    {
252        CHECK_CME();
253
254        int temp = hasNextVectorPos;
255
256        hasPrevVectorPos = hasNextVectorPos = -1;
257        modifiedSince = false;
258
259        if (temp != -1) return temp;
260
261        Object o; // Temp Object
262
263        int LOOP_BOUNDARY = (maxCursor == -1) ? (v.size() - 1) : maxCursor;
264
265        if (cursor == -1) cursor = (minCursor == -1) ? -1 : (minCursor-1);
266
267        while (++cursor <= LOOP_BOUNDARY)
268
269            if (c.isInstance(o = v.elementAt(cursor)) && p.test(c.cast(o)))
270                return cursor;
271
272        throw new NoSuchElementException("There are no more 'next' elements available.");
273    }
274
275
276    // ********************************************************************************************
277    // ********************************************************************************************
278    // "First" and "Last" - Retrieval Operations
279    // ********************************************************************************************
280    // ********************************************************************************************
281
282
283    /**
284     * Convenience Method.
285     * <BR />Invokes: {@link #firstIndex()}
286     * <BR />Retrieves: {@code 'E'} node-instance from the {@code Vector}
287     */
288    public E first()
289    { return (E) v.elementAt(firstIndex()); }
290
291    /**
292     * This method will "reset the internal {@code cursor}" to the beginning, and return the index
293     * of the first node-match (rather than the node itself).
294     *
295     * <EMBED CLASS='external-html' DATA-FILE-ID=CMERESET>
296     *
297     * @return The index of the very-first node-match in this list.  As with other
298     * {@code Iterator}-retrieval methods, if the underlying {@code Vector} has been changed using
299     * calls to: {@code set, remove,} or {@code add}, then this method will return the
300     * first-integer index of the node-match for the modified-{@code Vector}.
301     */
302    public int firstIndex()
303    {
304        cursor              = (minCursor == -1) ? 0 : minCursor;
305        hasNextVectorPos    = hasPrevVectorPos = -1;
306        expectedSize        = v.size();
307        
308        // NOTE: A call to first, last, firstIndex, or lastIndex
309        // "resets" the CME Monitor-Logic ==> expectedSize = v.size();
310
311        return nextIndex();
312    }
313
314    /**
315     * Convenience Method.
316     * <BR />Invokes: {@link #lastIndex()}
317     * <BR />Retrieves: {@code 'E'} node-instance from the {@code Vector}
318     */
319    public E last()
320    { return (E) v.elementAt(lastIndex()); }
321
322    /**
323     * This method will "advance the internal {@code cursor}" to the end of the {@code Vector}, and
324     * return the index of the last node-match (rather than the node itself).
325     *
326     * <EMBED CLASS='external-html' DATA-FILE-ID=CMERESET>
327     *
328     * @return The index of the very-last node-match in this list.  As with other
329     * {@code Iterator}-retrieval methods, if the underlying {@code Vector} has been changed using
330     * calls to: {@code set, remove,} or {@code add}, then this method will return the
331     * last-integer index of the node-match for the modified-{@code Vector}.
332     */
333    public int lastIndex()
334    {
335        cursor              = (maxCursor == -1) ? (v.size() - 1) : maxCursor;
336        hasNextVectorPos    = hasPrevVectorPos = -1;
337        expectedSize        = v.size();
338
339        // NOTE: A call to first, last, firstIndex, or lastIndex
340        // "resets" the CME Monitor-Logic ==> expectedSize = v.size();
341
342        return previousIndex();
343    }
344}