001package Torello.HTML;
002
003import java.util.*;
004
005import java.util.function.ObjIntConsumer;
006
007// This is only needed for the Java Doc {@link ...} taglets.
008import Torello.HTML.NodeSearch.TagNodePeekL1Inclusive;
009
010import Torello.Java.LV;
011import Torello.Java.StringParse;
012import Torello.Java.Additional.Ret2;
013
014/**
015 * Methods for quickly & efficiently replacing the nodes on a Web-Page.
016 * 
017 * <BR /><BR /><EMBED CLASS='external-html' DATA-FILE-ID=RN>
018 */
019@Torello.JavaDoc.StaticFunctional
020public class ReplaceNodes
021{
022    private ReplaceNodes() { }
023
024    /**
025     * <EMBED CLASS='external-html' DATA-FILE-ID=RN_R01_DESC>
026     * 
027     * @param page <EMBED CLASS='external-html' DATA-FILE-ID=RN_R01_PAGE>
028     * @param updatedReplaceables <EMBED CLASS='external-html' DATA-FILE-ID=RN_R01_UPDATEREPL>
029     * @param updateReplaceablesAfterBuild <EMBED CLASS='external-html' DATA-FILE-ID=RN_R01_URAB>
030     * 
031     * @return <EMBED CLASS='external-html' DATA-FILE-ID=RN_R01_RET>
032     * 
033     * @throws ReplaceableOutOfBoundsException  If any of the {@link Replaceable} instances returned
034     * by the {@code 'updatedReplaceables'} iterator-parameter have <B><I>original location</I></B>
035     * {@code Vector}-indices that are not within the bounds of the HTML page-{@code Vector}
036     * (parameter {@code 'page'}).
037     * 
038     * @throws ReplaceablesOverlappingException If any of the {@link Replaceable} instances returned
039     * by the {@code 'updatedReplaceables'} iterator-parameter have <B><I>original location</I></B>
040     * {@code Vector}-indices that overlap.
041     * 
042     * @throws ReplaceablesUnsortedException If any of the {@link Replaceable} instances returned
043     * by the {@code 'updatedReplaceables'} iterator-parameter have <B><I>original-starting 
044     * locations</I></B> that are non-consecutive (out of order!)
045     * 
046     * <!-- In the JD Comments 'external-html/' directory, this is filed under 'r0/' -->
047     */
048    public static
049        Ret2<Vector<HTMLNode>, Vector<Replaceable>>
050        r(
051            Vector<HTMLNode> page, Iterable<? extends Replaceable> updatedReplaceables,
052            boolean updateReplaceablesAfterBuild
053        )
054    {
055        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
056        // First check for the case that 'updatedReplaceables' is empty
057        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
058
059        final int SIZE = page.size();
060
061        // This entire loop is merely done for nothing more than error/exception checking.
062        // It is mandatory that the SubSections which are passed are all 'in-order', that
063        // none of them overlap, and that they all fit inside the 'page' vector parameter.
064
065        Iterator<? extends Replaceable> iter = updatedReplaceables.iterator();
066
067        // If there are no Replaceables in the Iterable, return the original page.
068        if (! iter.hasNext())
069        {
070            if (! updateReplaceablesAfterBuild) return new Ret2<>(page, null);
071
072            Vector<Replaceable> ret = new Vector<>();
073            for (Replaceable r : updatedReplaceables) ret.add(r);
074            return new Ret2<>(page, ret);
075        }
076
077
078        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
079        // Initialize the Loop variables
080        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
081
082        Replaceable replaceable         = iter.next();
083        Replaceable previousReplaceable = null;
084
085        // These are used, specifically, for the error-checking part of the loop
086        int start1  = replaceable.originalLocationStart();
087        int end1    = replaceable.originalLocationEnd() - 1;  // Value is Exclusive
088        int start2  = -1;
089        int end2    = -1;
090
091        // This is used for the exception messages only.  It is incremented on the last line of the
092        // loop body.
093
094        int i=0;
095
096        // These are used, specifically, for the part that computes the size the final vector
097        int size = 0;   // Total (Future) Size of the Return / Output Vector
098        int last = 0;   // Temp Variable, it is easier to have a separate one for this
099
100        while (iter.hasNext())
101        {
102            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
103            // Compute what the size of the returned HTML-Vector is going to be.
104            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
105            //
106            // This is done by looking at the locations of all the replacements, and the number of
107            // nodes between each replacement.
108            //
109            // For the part of this loop that is computing the size of the final vector, there
110            // start2 and end2 pointers should just be ignored.  The start1, end1 pointer pair
111            // are sufficient, as during each iteration, start2 and end2 are assigned to start1 and
112            // end1 in the very next step anyways.
113    
114            size += (start1 - last);            // Size of the previous "in-between chunk"
115            size += replaceable.currentSize();  // Size of the next SubSection
116            last = end1;                        // advance the 'last' pointer
117
118        
119            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
120            // Advance the Validity-Checking Pointer Pairs
121            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
122
123            // Advance pointer-pair #1 (but DONT'T do this one the VERY FIRST ITERATION)
124            if (i > 0)
125            {
126                start1  = start2;
127                end1    = end2;
128            }
129
130            // Advance Pointer Pair #2
131            previousReplaceable = replaceable;
132            replaceable         = iter.next();
133            start2              = replaceable.originalLocationStart();
134            end2                = replaceable.originalLocationEnd() - 1;
135
136
137            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
138            // NOW... THE VALIDITY-CHECKING IF-STATEMENTS
139            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
140
141            /*
142            System.out.println(
143                "previousReplaceable: " + previousReplaceable +
144                ", replaceable: " + replaceable + '\n' +
145                "start1: " + start1 + ", end1: " + end1 + ", start2: " + start2 + ", end2: " + end2
146            );
147            */
148
149            if (start2 < start1)
150
151                throw new ReplaceablesUnsortedException(
152                    "'updatedReplaceables' contains at least one Replaceable Element-Pair " +
153                    "which is not sorted from first to last:\n" +
154                    "The " + (i+1) + StringParse.ordinalIndicator(i+1) + " Replaceable returned " +
155                    "by 'updatedReplaceables' starts at page-index " + start1 + '\n' +
156                    "The " + (i+2) + StringParse.ordinalIndicator(i+2) + " Replaceable returned " +
157                    "by 'updatedReplaceables' starts at page-index " + start2,
158                    previousReplaceable, replaceable
159                );
160
161            if (    (start2 == start1)  // New section starts at same place as the previous section
162                ||  (start2 <= end1)    // New section begins before the previous section ended
163    
164                // !!! Whenever a user has created a zero-length-replaceable (zero original length)
165                // then the "end" of that replaceable will be "start-1".  Sounds a little silly,
166                // right?  Well inserting a zero-length replaceable happens a lot in JavaDoc
167                // Upgrader.  The two cases of the if-statement are both important.  Remember, the
168                // iterator must be returning sorted elements, or else the previous if statement
169                // would have already failed.
170                )
171
172                throw new ReplaceablesOverlappingException(
173                    "'updatedReplaceables' contains at least one Replaceable Element-Pair " +
174                    "that overlap each-other:\n" +
175                    "The " + (i+1) + StringParse.ordinalIndicator(i+1) + " Replaceable returned " +
176                    "by 'updatedReplaceables' has original-location " +
177                    "[" + start1 + ", " + end1 + "]\n" +
178                    "The " + (i+2) + StringParse.ordinalIndicator(i+2) + " Replaceable returned " +
179                    "by 'updatedReplaceables' has original-location " +
180                    "[" + start2 + ", " + end2 + ']',
181                    previousReplaceable, replaceable
182                );
183
184            if (end1 > SIZE)
185
186                throw new ReplaceableOutOfBoundsException(
187                    "There was a Replaceable Element whose original-location was not within the " +
188                    "bounds of page:\n" +
189                    "The " + (i+1) + StringParse.ordinalIndicator(i+1) + " Replaceable returned " +
190                    "by 'updatedReplaceables' has original-location " +
191                    "[" + start1 + ", " + end1 + "]\n" +
192                    "While page.size() is: " + SIZE,
193                    replaceable
194                );
195
196            i++;
197        }
198
199
200        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
201        // POST-LOOP FINISHING TOUCHES
202        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
203
204        // "Ending Read" Check.  The very last location is not checked, because the loop breaks
205        // before it gets to check pointer-pair-2 (on the last iteration)
206        //
207        // NOTE: The patholigical-cae where there is **ONLY ONE** SubSection in the updatedReplaceables
208        //       input Collection.  If pointer-pair-2 is -1, there is no need to check it... :)
209        //       If (pointer-pair-2 == -1), the loop body was never entered
210
211        if (end2 != -1) if (end2 >= SIZE)
212
213            throw new ReplaceableOutOfBoundsException(
214                "There was a Replaceable Element whose original-location was not within the " +
215                "bounds of page:\n" +
216                "The " + (i+2) + StringParse.ordinalIndicator(i+2) + " Replaceable returned by " +
217                "'updatedReplaceables' has original-location [" + start2 + ", " + end2 + "]\n" +
218                "While page.size() is: " + SIZE,
219                replaceable
220            );
221
222        // the very-last replaceable was not added to the size.
223        size += (start1 - last);            // Size of the previous "in-between chunk"
224        size += replaceable.currentSize();  // Size of the next SubSection
225        last = end1;
226
227
228        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
229        // Build the Return Vector - NOTE - We just computed its final size!
230        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
231        //
232        // ALSO: If the user has requested the DP's be udated, also build the "newSubSections" Vec
233        //
234        // The purpose of the above computation was for instantiating a properly-sized vector
235        // at construction time.  This will save quite a bit of time that would be wasted on
236        // vector resizing.
237
238        Vector<HTMLNode> ret = new Vector<>(size);
239
240        // By user request, only!  This really isn't *THAT* important.  All that the
241        // 'newSubSections' Vector will have shall be the exact same-subsections that are passed
242        // as a parameter to this method through the 'updatedReplaceables' parameter - *EXCEPT* that
243        // their SubSection.location fields will be updated to hold the *ACTUAL* / *NEW* locations
244
245        Vector<Replaceable> newReplaceables =
246            updateReplaceablesAfterBuild ? new Vector<>() : null;
247
248
249        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
250        // MAIN-LOOP: Iterate each of the Replaceables that was passed as input to this method.
251        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
252        //
253        // Add their contents to Output-Vector, and make sure to add all "in-between" nodes too!
254
255        // The index-pointer to the **ORIGINAL-VECTOR** (a.k.a. the input vector)
256        int pagePos=0;
257
258        // This loop does the replacement.  It is quick and easy if you understand what replacing
259        // a list of subsections involves.
260
261        for (Replaceable r : updatedReplaceables)
262        {
263            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 
264            // Add all of the MOST-RECENT "In-Between Nodes"  (These are all nodes before next SS)
265            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
266            //
267            // AFTERWARDS: Add all nodes in the next Sub-Section 
268
269            // Retrieve all of the 'in-between' nodes
270            while (pagePos < r.originalLocationStart()) ret.add(page.elementAt(pagePos++));
271
272            // Add this Replaceable to the returned output list!
273            r.addAllInto(ret);
274
275            // Skip over the old nodes.
276            pagePos = r.originalLocationEnd(); // don't add one, value is exclusive
277
278
279            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 
280            // User may request that the Sub-Section 'Locations' be updated, rather than discarded
281            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 
282
283            // This is done for convenience so that the user knows where the sections are all
284            // located in the new build.
285            //
286            // NOTE: All this is doing is changing the 'location' field of the old subsection
287            //       which has changed to contain the new 'location'
288
289            if (updateReplaceablesAfterBuild)
290            {
291                int ePos = ret.size();
292                int sPos = ePos - r.currentSize() + 1;
293
294                newReplaceables.add(r.moveAndUpdate(sPos));
295            }
296        }
297
298
299        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
300        // IMPORTANT: Add the last / final Elements that occur *AFTER* the *LAST* Sub-Section
301        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
302        //
303        // This part should also be called "The Tail" of the Page.  (Put the Page-Tail back)
304
305        while (pagePos < page.size()) ret.add(page.elementAt(pagePos++));
306
307
308        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
309        // AGAIN: User may request that Sub-Section 'Locations' be updated, rather than discarded
310        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
311
312        return updateReplaceablesAfterBuild
313            ? new Ret2<>(ret, newReplaceables)
314            : new Ret2<>(ret, null);
315    }
316
317    /**
318     * Allows a user to quickly alter each <B STYLE='color: red;'>row</B> in an HTML
319     * <B STYLE='color: red;'>table</B>, iteratively, in a manner that offers a tremendous
320     * efficiency-improvement over the HTML {@code Iterator's} in the Node Seach Package.
321     * 
322     * <EMBED CLASS='external-html' DATA-FILE-ID=RN_R02_R03_FAST DATA-ITEM='table-row'>
323     * 
324     * <BR /><BR /><UL CLASS=JDUL>
325     * <LI> {@code 'page'} is not null.
326     *      </LI>
327     * <LI> {@code 'table'} is non-null, and has {@link DotPair#start} and {@link DotPair#end}
328     *      indices with integer-values smaller than the size of {@code 'page'}
329     *      </LI>
330     * <LI> {@code 'table'} has indices which point to {@code TagNode's} in {@code 'page'} that
331     *      are opening {@code <TABLE>} and closing {@code </TABLE>} tags.
332     *      </LI>
333     * </UL>
334     * 
335     * @param page Any HTML Page or sub-page that has a table.
336     * 
337     * @param table A pointer to the table start and end index bounds.
338     * 
339     * @param tableRowModifier <EMBED CLASS='external-html' DATA-TYPE=Table DATA-ENTITY=Row 
340     *      DATA-FILE-ID=RN_R02_R03_ROW_MOD>
341     * 
342     * @return <EMBED CLASS='external-html' DATA-FILE-ID=RN_R01_RET>
343     * <!-- Bororwed from R01 RET -->
344     * 
345     * @see TagNodePeekL1Inclusive
346     * @see ReplaceNodes#r(Vector, Iterable, boolean)
347     * @see DotPair#exceptionCheck(Vector, String[])
348     * @see SubSection
349     * <!-- In the JD Comments 'external-html/' directory, this is filed under 'r02r03/' -->
350     */
351    public static Vector<HTMLNode> tableTR(
352            Vector<HTMLNode> page, DotPair table,
353            ObjIntConsumer<Vector<HTMLNode>> tableRowModifier
354        )
355    {
356        // Ensure that page.elementAt(table.start) contains a "<TABLE>" element, and that
357        // page.elementAt(table.end) contains a "</TABLE>" element.
358        //
359        // If some other type of TagNode, or a non-TagNode is present, this method throws one of
360        // several exceptions to inform the user about the error.
361
362        table.exceptionCheck(page, "table");
363
364        // Retrieve all "<TR> ... </TR>" elements.  The "L1Inclusive" stipulates that any potential
365        // inner-table rows (if there are any inner-tables), should be ignored.
366
367        Vector<SubSection> rows = TagNodePeekL1Inclusive.all(page, table.start, table.end, "tr");
368
369        // All this does is invoke the user-provided function-pointer on each table-row
370        for (int i=0; i < rows.size(); i++) tableRowModifier.accept(rows.elementAt(i).html, i);
371
372        // Update all Table-Rows.  Remove the old Rows, and insert the new ones.  This replace
373        // operation does this much more efficiently than most replacement-code.
374
375        return ReplaceNodes.r(page, rows, false).a;
376    }
377
378    /**
379     * Allows a user to quickly alter each <B STYLE='color: red;'>item</B> in an HTML
380     * <B STYLE='color: red;'>list</B> ({@code <OL>, <UL>} or {@code <MENU>}), iteratively, in a
381     * manner that offers a tremendous efficiency-improvement over the HTML {@code Iterator's} in
382     * the Node Seach Package.
383     * 
384     * <EMBED CLASS='external-html' DATA-FILE-ID=RN_R02_R03_FAST DATA-ITEM='list-item'>
385     * 
386     * <BR /><BR /><UL CLASS=JDUL>
387     * <LI> {@code 'page'} is not null.
388     *      </LI>
389     * <LI> {@code 'list'} is non-null, and has {@link DotPair#start} and {@link DotPair#end}
390     *      indices with integer-values smaller than the size of {@code 'page'}
391     *      </LI>
392     * <LI> {@code 'list'} has indices which point to {@code TagNode's} in {@code 'page'} that
393     *      are opening-and-closing {@code <UL>, <OL>} or {@code <MENU>} tags.
394     *      </LI>
395     * </UL>
396     * 
397     * @param page Any HTML Page or sub-page that has an {@code <OL>, <UL>} or {@code <MENU>}.
398     * 
399     * @param list A pointer to the list start and end index bounds.
400     * 
401     * @param listItemModifier <EMBED CLASS='external-html' DATA-TYPE=List DATA-ENTITY=Item 
402     *      DATA-FILE-ID=RN_R02_R03_ROW_MOD>
403     * 
404     * @return <EMBED CLASS='external-html' DATA-FILE-ID=RN_R01_RET> <!-- Bororwed from R01 RET -->
405     * 
406     * @see TagNodePeekL1Inclusive
407     * @see ReplaceNodes#r(Vector, Iterable, boolean)
408     * @see DotPair#exceptionCheck(Vector, String[])
409     * @see SubSection
410     * <!-- In the JD Comments 'external-html/' directory, this is filed under 'r02r03/' -->
411     */
412    public static Vector<HTMLNode> listLI(
413            Vector<HTMLNode> page, DotPair list,
414            ObjIntConsumer<Vector<HTMLNode>> listItemModifier
415        )
416    {
417        // Ensure that page.elementAt(list.start) contains an "<OL>", "<UL>", or "<MENU>" element,
418        // and that page.elementAt(list.end) contains an "</OL>", "</UL>" or "</MENU>" element.
419        //
420        // If some other type of TagNode, or a non-TagNode is present, this method throws one of
421        // several exceptions to inform the user about the error.
422
423        list.exceptionCheck(page, "ol", "ul", "menu");
424
425        // Retrieve all "<LI> ... </LI>" elements.  The "L1Inclusive" stipulates that any potential
426        // inner-list items (if there are any inner-lists), should be ignored.
427
428        Vector<SubSection> items = TagNodePeekL1Inclusive.all(page, list.start, list.end, "li");
429
430        // All this does is invoke the user-provided function-pointer on each list-item
431        for (int i=0; i < items.size(); i++) listItemModifier.accept(items.elementAt(i).html, i);
432
433        // Update all items.  Remove the old-Items, and insert the new ones.  This replace
434        // operation does this much more efficiently than most replacement-code.
435
436        return ReplaceNodes.r(page, items, false).a;
437    }
438
439    /**
440     * Iterates the integer-pointer values in {@code int[] posArr}, and replaces the nodes inside
441     * {@code 'html'} that have been identified by the {@code 'posArr'} list with a new node
442     * supplied by the {@code interface ReplaceFunction}.  It should be obvious, that lambda
443     * expressions may be used here.
444     * 
445     * <BR /><BR /><B CLASS=JDDescLabel>Returning 'null':</B>
446     * 
447     * <BR />If the Lambda-Target / Functional-Interface {@link ReplaceFunction} (whose method
448     * is named {@link ReplaceFunction#getReplacement(HTMLNode, int, int)}
449     * <B><I STYLE='color: red;'>actually returns <CODE>'null'</CODE></I></B>,
450     * then null will indeed by inserted into the corresponding {@code Vector} position.
451     * 
452     * @param html Any HTML page or section that has been loaded already.
453     * 
454     * @param posArr This is usually generated by one of the node-search {@code 'Find.all(...)'}
455     * methods.  Each and every one of the {@code 'Find.all(...)'} methods in the search package
456     * will return an array of integers.  These integers represent positions/locations in the
457     * passed HTML page {@code Vector}.
458     * 
459     * @param rf This is just a class that implements the {@code interface ReplaceFunction}.  The
460     * interface has a single-method that will receive the position in the {@code Vector}, along
461     * with the {@code HTMLNode} found at that location.  It is expected to produce a new version
462     * of the {@code HTMLNode}.  This new node will be substituted into the page or sup-page.
463     * 
464     * <BR /><BR />This function-pointer may return null, and when it does, <I>the node located at
465     * the current loop-iteration's {@code Vector}-index will not be replaced</I>.
466     * 
467     * @return The number of nodes that were succesfully replaced.  This number will be equal to
468     * {@code posArr.length} minus the number of times {@code rf} returned null;
469     * 
470     * @throws ArrayIndexOutOfBoundsException <B><SPAN STYLE="color: red;">IMPORTANT
471     * NOTE:</B></SPAN> Usually, a position-{@code array} is generated by one of the search-methods
472     * in the NodeSearch package.  If, however, the {@code Vector} has since changed and
473     * {@code posArr} contains stale-data, or if for other reasons there are invalid index-pointers
474     * in {@code posArr}, then an {@code ArrayIndexOutOfBoundsException} will, naturally, be thrown
475     * by java's {@code Vector.setElementAt(...)} method.
476     * 
477     * @see ReplaceFunction
478     */
479    public static int r(Vector<HTMLNode> html, int[] posArr, ReplaceFunction rf)
480    {
481        int counter=0, numReplaced=0;
482        HTMLNode n;
483
484        for (int pos : posArr)
485
486            // pos is the vector-position, counter is the "iteration-count"
487            if ((n = rf.getReplacement(html.elementAt(pos), pos, counter++)) != null)
488            {
489                html.setElementAt(n, pos);
490                numReplaced++;
491            }
492
493        return numReplaced;
494    }
495
496    /**
497     * This will replace <I>each and every node indicated by {@code 'posArr'}</I> with the
498     * exact same replacement node {@code 'n'}.
499     *
500     * @param html Any HTML page or section that has been loaded already.
501     * 
502     * @param posArr This is usually generated by one of the node-search {@code 'Find.all(...)'}
503     * methods.  Each and every one of the {@code 'Find.all(...)'} methods in the search package
504     * will return an array of integers.  These integers represent positions/locations in the
505     * passed HTML page {@code Vector}.
506     *
507     * @param n This may be any non-null {@code HTMLNode}.  This node shall be inserted (and will
508     * replace) each node indicated by the parameter {@code 'posArr'}.
509     * 
510     * @throws ArrayIndexOutOfBoundsException <B><SPAN STYLE="color: red;">IMPORTANT
511     * NOTE:</B></SPAN> Usually, a position-{@code array} is generated by one of the search-methods
512     * in the NodeSearch package.  If, however, the {@code Vector} has since changed and
513     * {@code posArr} contains stale-data, or if for other reasons there are invalid index-pointers
514     * in {@code posArr}, then an {@code ArrayIndexOutOfBoundsException} will, naturally, be thrown
515     * by java's {@code Vector.setElementAt(...)} method.
516     */
517    public static void r(Vector<HTMLNode> html, int[] posArr, HTMLNode n)
518    { 
519        int len= html.size();
520
521        for (int i=0; i < len; i++) html.setElementAt(n, i);
522    }
523
524    /**
525     * Convenience Method.
526     * <BR />Invokes: {@link #r(Vector, int, int, ReplaceFunction)}
527     * <BR />Passes: Entire Range of {@code html} into {@code sPos & ePos}
528     */
529    public static int r(Vector<HTMLNode> html, ReplaceFunction rf)
530    { return r(html, 0, -1, rf); }
531
532    /**
533     * Convenience Method.
534     * <BR />Invokes: {@link #r(Vector, int, int, ReplaceFunction)}
535     * <BR />Passes: {@link DotPair#start} &amp; {@link DotPair#end} into {@code sPos & ePos}
536     */
537    public static int r(Vector<HTMLNode> html, DotPair dp, ReplaceFunction rf)
538    { return r(html, dp.start, dp.end + 1, rf); }
539
540    /**
541     * Iterates <I>the entire html-page, checking every node</I>, replacing them by the
542     * values returned by {@code 'rf'} (the replace function).  The {@link ReplaceFunction}
543     * (parameter {@code 'rf'}) is expected to return values that either:
544     * 
545     * <BR /><BR /><UL CLASS=JDUL>
546     * <LI>provide a replacement {@code HTMLNode} for the indicated position</LI>
547     * <LI>return null as a value - in which case, <I>no substitution will occur</I></LI>
548     * </UL>
549     *
550     * <BR /><BR /><B CLASS=JDDescLabel>Returning 'null':</B>
551     * 
552     * <BR />If the Lambda-Target / Functional-Interface {@link ReplaceFunction} (whose method
553     * is named {@link ReplaceFunction#getReplacement(HTMLNode, int, int)}
554     * <B><I STYLE='color: red;'>returns <CODE>'null'</CODE></I></B>,
555     * then in this particular method (differing from a previous method in this class), the
556     * returned 'null' will be ignored, and no substitution will be performed.
557     * 
558     * @param html Any HTML page or section that has been loaded already.
559     * 
560     * @param sPos <EMBED CLASS='external-html' DATA-FILE-ID=SPOSVEC>
561     * @param ePos <EMBED CLASS='external-html' DATA-FILE-ID=EPOSVEC>
562     * 
563     * @param rf This is just a class that implements the {@code interface ReplaceFunction}.  The
564     * interface has a single-method (@code getReplacement(...)} that will receive the position in
565     * the {@code Vector}, along with the {@code HTMLNode} found at that location.  It is expected
566     * to return a new version of the {@code HTMLNode}.
567     * 
568     * <BR /><BR />This function-pointer may return null, and when it does, <I>the node located at
569     * the current loop-iteration's {@code Vector}-index will not be replaced</I>.
570     * 
571     * @return The number of nodes that were succesfully replaced.  This number will be equal to
572     * {@code html.size()} minus the number of times {@code rf} returned null;
573     * 
574     * @throws IndexOutOfBoundsException <EMBED CLASS='external-html' DATA-FILE-ID=VIOOBEX>
575     * 
576     * @see ReplaceFunction
577     * @see HTMLNode
578     */
579    public static int r(Vector<HTMLNode> html, int sPos, int ePos, ReplaceFunction rf)
580    {
581        LV  l           = new LV(html, sPos, ePos);
582        int numReplaced = 0;
583
584        for (int i=l.start; i < l.end; i++)
585        {
586            // Here the vector-position and iteration-number are the same
587            HTMLNode n = rf.getReplacement(html.elementAt(i), i, i);
588
589            if (n != null)
590            {
591                html.setElementAt(n, i);
592                numReplaced++;
593            }
594        }
595
596        return numReplaced;
597    }
598
599    /**
600     * Iterates the integer-pointer values listed by {@code 'posArr'}, and replaces every position
601     * in {@code 'html'} with an {@code HTMLNode} from the nodes provided by the {@code 'newNodes'}
602     * parameter.
603     * 
604     * @param html Any HTML page or section that has been loaded already.
605     * 
606     * @param posArr This is usually generated by one of the node-search {@code 'Find.all(...)'} 
607     * methods.  Each and every one of the {@code 'Find.all(...)'} methods in the search package 
608     * will return an {@code int[] array}.  These integers represent positions/locations in the
609     * passed HTML page {@code Vector.}
610     * 
611     * @param newNodes This list of new nodes must have a length identical to the
612     * {@code int[] posArr} (pointer-Array) length.
613     * 
614     * @throws ArrayIndexOutOfBoundsException This exception will throw if any of the elements of
615     * {@code 'posArr'} point to a position in the {@code Vector<HTMLNode> v} parameter that are
616     * out of bounds for that {@code Vector}.
617     * 
618     * @throws IllegalArgumentException if the length of the position array (pointer-array) is not
619     * identical to the length of the new-nodes {@code Vector.}
620     */
621    public static void r(Vector<HTMLNode> html, int[] posArr, Vector<HTMLNode> newNodes)
622    {
623        if (posArr.length != newNodes.size()) throw new ArrayIndexOutOfBoundsException(
624            "The pointer array 'posArr', and the replacement-node array 'newNodes' do not have " +
625            "equal lengths!\n" +
626            "posArr.length=" + posArr.length + ", newNodes.size()=" + newNodes.size()
627        );
628
629        int newNodesPos = 0;
630
631        for (int pos : posArr) html.setElementAt(newNodes.elementAt(newNodesPos++), pos);
632    }
633
634    /**
635     * Convenience Method.
636     * <BR />Invokes: {@link #r(Vector, int, int, Vector)}
637     * <BR />Assumes: {@code rangeWithUpdates.html} is located in {@code html} at
638     * {@code rangeWithUpdates.location}
639     * <BR />And: That {@code rangeWithUpdates.html} has been changed, and needs updating.
640     */
641    public static int r(Vector<HTMLNode> html, SubSection rangeWithUpdates)
642    {
643        return r(
644            html, rangeWithUpdates.location.start, rangeWithUpdates.location.end,
645            rangeWithUpdates.html
646        );
647    }
648
649    /**
650     * Convenience Method.
651     * <BR />Invokes: {@link #r(Vector, int, int, Vector)}
652     */
653    public static int r(Vector<HTMLNode> html, DotPair range, Vector<HTMLNode> newNodes)
654    { return r(html, range.start, range.end + 1, newNodes); }
655
656    /**
657     * Replaces the nodes currently within the vectorized HTML parameter {@code 'html'}, in the
658     * sub-range provided by the {@code 'sPos'} and {@code 'ePos'} parameters - <I>using the new
659     * nodes provided by {@code Vector}-Parameter {@code 'newNodes'}.</I>  This is, essentially,
660     * a sub-range array-replacement operation.
661     * 
662     * <BR /><BR />Unless exactly the same number of nodes that are in the {@code 'replaceRange'}
663     * are also in {@code 'newNodes'}, this method shall have to shorten or lengthen the size of
664     * the HTML {@code Vector}.
665     * 
666     * @param html This may be any HTML page or sub-page
667     * @param sPos <EMBED CLASS='external-html' DATA-FILE-ID=SPOSVEC>
668     * @param ePos <EMBED CLASS='external-html' DATA-FILE-ID=EPOSVEC>
669     * @param newNodes These are the new {@code HTMLNode's} that are to replace the old ones.
670     * @return The change in the size (size-delta) of the input {@code html} parameter.
671     * @throws IndexOutOfBoundsException <EMBED CLASS='external-html' DATA-FILE-ID=VIOOBEX>
672     */
673    public static int r(Vector<HTMLNode> html, int sPos, int ePos, Vector<HTMLNode> newNodes)
674    {
675        // The loop variable is needed because its constructor does all of the error checking.
676        // The constructor also checks for the negative-ePos, and changes it if it is negative
677        //
678        // The original version of this method has been deprected, and left as a private method to
679        // this class.  It was before noticing the "mirrored" stipulations about Vector-operation
680        // "subList"  View the source-code to see the original replace-range method.
681
682        LV l = new LV(html, sPos, ePos);
683
684        List<HTMLNode> list = html.subList(l.start, l.end);
685
686        // The Sun-Oracle Docs say that changes to the list returned by sub-list are mirrored into
687        // changes in the original vector.  This is how sub-range operations are done.
688
689        list.clear();
690        list.addAll(newNodes);
691
692        return newNodes.size() - (ePos - sPos); // ==> (newSize - originalSize)
693    }
694
695    // This was how to do this, until noticing in the Java-Docs that "subList" is "linked"
696    // to the original list!
697    private static void rOLD(Vector<HTMLNode> html, int sPos, int ePos, Vector<HTMLNode> newNodes)
698    {
699        LV l = new LV(html, sPos, ePos);
700
701        // Replacement while-loop.  This will replace all nodes that can be replaced in the
702        // "replace range" - without growing or shrinking the size of the underlying vector.
703        //
704        // AFTERWARDS   If there are still more nodes to insert, they will be inserted by
705        //              growing the vector.
706        //
707        // AND ALSO     If there were fewer nodes to insert than the number that need to be
708        //              removed the Vector will be shortened.
709
710        int i = 0;
711        int j = sPos;
712
713        while ((j < l.end) && (i < newNodes.size()))
714        {
715            html.setElementAt(newNodes.elementAt(i), j);
716
717            i++; j++;
718        }
719
720
721        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
722        // CASE 1: The number of nodes to remove is precisely equal to the number being inserted.
723        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
724
725        if (l.size() == newNodes.size()) return;
726
727    
728        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
729        // CASE 2: The number of nodes being removed is greater than the number being inserted
730        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
731
732        // Use Util.removeRange(...) to remove the remaining nodes that weren't replaced.
733        // More nodes need to be removed than the number of nodes that have already been inserted
734        // (in the previous loop).  In this case the original HTML Vector will SHRINK in SIZE.
735
736        if (j < l.end) { Util.Remove.range(html, j, l.end);  return; }
737
738
739        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
740        // CASE 3: There are more nodes being added than the number of nodes being removed.
741        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
742
743        // Use Vector.addAll(...) to add the rest which haven't been inserted yet.
744        // The HTML Vector is going to grow.  There were fewer nodes removed than the number of
745        // nodes that need to be inserted.
746        //
747        // NOTE: An "intermediate temp Vector" is used since the "Node Shift" is likely done more
748        //       efficiently by 'Vector.addAll', than by calling 'Vector.add' multiple times.
749
750        Vector<HTMLNode> temp = new Vector<>(newNodes.size() - i);
751
752        while (i < newNodes.size()) temp.add(newNodes.elementAt(i++));
753
754        html.addAll(l.end, temp);
755    }
756
757    /**
758     * Convenience Method.
759     * Invokes: {@link #r(Vector, int, int, HTMLNode)}
760     */
761    public static int r(Vector<HTMLNode> html, DotPair range, HTMLNode newNode)
762    { return r(html, range.start, range.end + 1, newNode); }
763
764    /**
765     * Replaces the nodes currently within the vectorized HTML parameter {@code 'html'}, in the
766     * sub-range provided by the {@code 'sPos'} and {@code 'ePos'} parameters, <I>with a single
767     * new node provided by {@code 'newNode'}.</I>  Essentially, this is a <B>Range Removal</B>
768     * Operation, because a complete sublist is removed, and only a single-node replaces it.
769     * 
770     * <BR /><BR />Unless the replacement-range (defined by {@code 'sPos'} and {@code 'ePos'}) has
771     * a size equal to {@code '1'}, this operation will (obviously) shorten the size of the input
772     * HTML {@code Vector} by {@code size - 1} nodes.
773     * 
774     * @param html This may be any HTML page or sub-page
775     * 
776     * @param sPos <EMBED CLASS='external-html' DATA-FILE-ID=SPOSVEC>
777     * @param ePos <EMBED CLASS='external-html' DATA-FILE-ID=EPOSVEC>
778     * 
779     * @param newNode This is the new {@code HTMLNode} that is to replace the (entire) list
780     * specified by parameters {@code 'sPos'} and {@code 'ePos'}.
781     * 
782     * @return The change in the size (size-delta) of the input {@code html} parameter.
783     * The number returned will always equal {@code 1 - (ePos - sPos)}.  This means the return
784     * value for this method will always be negative (i.e. the {@code Vector} shrunk) - unless
785     * {@code sPos} and {@code ePos} had specified a range that was equal to 1.
786     * 
787     * @throws IndexOutOfBoundsException <EMBED CLASS='external-html' DATA-FILE-ID=VIOOBEX>
788     */
789    public static int r(Vector<HTMLNode> html, int sPos, int ePos, HTMLNode newNode)
790    {
791        // This method doesn't have any "for-loops", but the LV class does all the much needed
792        // exception checks, and conversion computations. (ePos < 0  ==>  epos = html.size())
793
794        LV l = new LV(html, sPos, ePos);
795
796        html.setElementAt(newNode, l.start);
797
798        if (l.size() > 1)
799
800            // Util.removeRange(html, l.start + 1, l.end);  // OLD-WAY
801            html.subList(l.start + 1, l.end).clear();       // NEW & IMPROVED WAY
802
803        return 1 - (ePos - sPos); // ==> (newSize - originalSize)
804    }
805
806    /**
807     * Replaces the instance of {@code HTMLNode} located at {@code Vector}-index {@code 'pos'}
808     * with the contents of {@code Vector} parameter {@code 'newNodes'}.  This removes just
809     * a single instance of {@code HTMLNode}, and replaces it with a list of nodes.
810     * <BR /><BR />Note that this method will, indeed, lengthen the size of the input HTML
811     * {@code Vector} (unless the {@code 'newNodes' Vector} being inserted has only 1 or 0
812     * elements).
813     *
814     * @param html This may be any HTML page or sub-page.
815     *
816     * @param replacePos The position of the {@code HTMLNode} to be removed and replaced with the
817     * list of nodes.
818     * 
819     * @param newNodes These are the new {@code HTMLNode's} that are to replace the old instance
820     * of {@code HTMLNode} at position {@code 'pos'}.
821     * 
822     * @return The change in the size (size-delta) of the input {@code html} parameter.
823     * The number returned will always equal {@code newNodes.size() - 1}
824     * 
825     * @throws ArrayIndexOutOfBoundsException This exception will throw if the specified 
826     * {@code 'pos'} parameter is not within the bounds of the {@code Vector}.
827     */
828    public static int r(Vector<HTMLNode> html, int replacePos, Vector<HTMLNode> newNodes)
829    {
830        if (replacePos < 0) throw new ArrayIndexOutOfBoundsException(
831            "The position passed to this method [" + replacePos + "] is negative."
832        );
833
834        if (replacePos >= newNodes.size()) throw new ArrayIndexOutOfBoundsException(
835            "The position passed to this method [" + replacePos + "] is greater than or equal " +
836            " to the size of the input HTML Vector parameter, 'html' [" + html.size() + "]"
837        );
838
839        html.removeElementAt(replacePos);
840        html.addAll(replacePos, newNodes);
841
842        return newNodes.size() - 1; // ==> (newSize - originalSize)
843    }
844}