001package Torello.HTML;
002
003import Torello.Java.StringParse;
004import Torello.Java.Additional.Ret2;
005import Torello.JavaDoc.StaticFunctional;
006
007import java.util.Vector;
008import java.util.Iterator;
009
010/**
011 * The means by which the {@link Replaceable} interface may be used to efficiently update a
012 * modified HTML-{@code Vector}, <B STYLE='color: red;'><I>quickly, and all-at-once</I></B>.
013 * 
014 * <BR /><BR />This class has but two public methods.  The both accept a single
015 * Java-{@code Vector}, and either a single {@link Replaceable}, or a list of
016 * {@link Replaceable Replaceable's}.  These {@code Replaceable's} must have been 'derived' from
017 * that self-same input {@code Vector}.
018 * 
019 * <BR /><BR />The class will iterate those User-Provided {@code Replaceable's},
020 * modifying the contents of the original / input {@code Vector} to reflect those changes.
021 * 
022 * <BR /><BR /><B CLASS=JDDescLabel2>Efficient-Transformations</B>
023 * 
024 * <BR />The sole benefit of the {@link Replaceable}-{@code interface} is such that it allows a
025 * programmer to avoid large numbers of <B STYLE='color: red;'>array-shift</B> and 
026 * <B STYLE='color: red;'>array-copy</B> operations.
027 * 
028 * <BR /><BR />If a user decides to modify the contents of an HTML-Table, for instance, he should
029 * first copy the {@link HTMLNode}-Contents of that table out of an input-{@code Vector} and into a
030 * Java-HTML {@link SubSection} instance.  Afterwards, the {@link HTMLNode}-Content of that 
031 * {@code SubSection} may be modified in any way that the user sees fit.
032 * 
033 * <BR /><BR />{@code SubSection's} of HTML have vastly smaller amounts of nodes inside
034 * their internal-array's (the Java-Package {@code 'java.util.*'} data-structures have array's 
035 * among their private data-fields).  Because the amount of nodes in an HTML-{@code SubSection}
036 * will always be fewer, the number of node-shifts that are required to modify that HTML will,
037 * obviously, always be tremendously fewer too.
038 * 
039 * <BR /><BR />By using the {@link Replaceable}-{@code interface} classes, a user may hold off the
040 * innefficent transformation of a Vectorized-HTML until all changes have been decided, at which
041 * point, they may be progpogated back into the original HTML-{@code Vector}, in a single pass 
042 * (using this class' sole-method) -<B STYLE='color: red;'><I>thereby avoiding large number of
043 * array-copy and shift operations!</I></B>
044 * 
045 * @see SubSection
046 * @see NodeIndex
047 */
048@Torello.JavaDoc.StaticFunctional
049public class Replacement 
050{
051    private Replacement() {}
052
053    /**
054     * <EMBED CLASS='external-html' DATA-FILE-ID=REPLACEMENT_MDESC>
055     * 
056     * @param page <EMBED CLASS='external-html' DATA-FILE-ID=REPLACEMENT_MPAGE>
057     * @param updatedReplaceables <EMBED CLASS='external-html' DATA-FILE-ID=REPLACEMENT_UREPLS>
058     * 
059     * @param updateReplaceablesAfterBuild
060     *  <EMBED CLASS='external-html' DATA-FILE-ID=REPLACEMENT_URAB>
061     * 
062     * @return <EMBED CLASS='external-html' DATA-FILE-ID=REPLACEMENT_MRET>
063     * <!-- NOTE: This HTML (for the Return-Description) is borrowed and used in ReplaceNodes -->
064     * 
065     * @throws ReplaceableOutOfBoundsException  If any of the {@link Replaceable} instances
066     * returned by the {@code 'updatedReplaceables'} iterator-parameter have <B><I>original
067     * location</I></B> {@code Vector}-indices that are not within the bounds of the HTML
068     * page-{@code Vector} (parameter {@code 'page'}).
069     * 
070     * @throws ReplaceablesOverlappingException If any of the {@link Replaceable} instances
071     * returned by the {@code 'updatedReplaceables'} iterator-parameter have <B><I>original
072     * location</I></B> {@code Vector}-indices that overlap.
073     * 
074     * @throws ReplaceablesUnsortedException If any of the {@link Replaceable} instances returned
075     * by the {@code 'updatedReplaceables'} iterator-parameter have <B><I>original-starting 
076     * locations</I></B> that are non-consecutive (out of order!)
077     * 
078     * <!-- In the JD Comments 'external-html/' directory, this is filed under 'r0/' -->
079     */
080    public static
081        Ret2<Vector<HTMLNode>, Vector<Replaceable>>
082        run(
083            final Vector<HTMLNode>                  page,
084            final Iterable<? extends Replaceable>   updatedReplaceables,
085            final boolean                           updateReplaceablesAfterBuild
086        )
087    {
088        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
089        // First check for the case that 'updatedReplaceables' is empty
090        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
091
092        final int SIZE = page.size();
093
094        // This entire loop is merely done for nothing more than error/exception checking.
095        // It is mandatory that the SubSections which are passed are all 'in-order', that
096        // none of them overlap, and that they all fit inside the 'page' vector parameter.
097
098        Iterator<? extends Replaceable> iter = updatedReplaceables.iterator();
099
100        // If there are no Replaceables in the Iterable, return the original page.
101        if (! iter.hasNext())
102        {
103            if (! updateReplaceablesAfterBuild) return new Ret2<>(page, null);
104
105            Vector<Replaceable> ret = new Vector<>();
106            for (Replaceable r : updatedReplaceables) ret.add(r);
107            return new Ret2<>(page, ret);
108        }
109
110
111        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
112        // Initialize the Loop variables
113        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
114
115        Replaceable replaceable         = iter.next();
116        Replaceable previousReplaceable = null;
117
118        // These are used, specifically, for the error-checking part of the loop
119        int start1  = replaceable.originalLocationStart();
120        int end1    = replaceable.originalLocationEnd() - 1;  // Value is Exclusive
121        int start2  = -1;
122        int end2    = -1;
123
124        // This is used for the exception messages only.  It is incremented on the last line of the
125        // loop body.
126
127        int i=0;
128
129        // These are used, specifically, for the part that computes the size the final vector
130        int size = 0;   // Total (Future) Size of the Return / Output Vector
131        int last = 0;   // Temp Variable, it is easier to have a separate one for this
132
133        while (iter.hasNext())
134        {
135            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
136            // Compute what the size of the returned HTML-Vector is going to be.
137            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
138            //
139            // This is done by looking at the locations of all the replacements, and the number of
140            // nodes between each replacement.
141            //
142            // For the part of this loop that is computing the size of the final vector, there
143            // start2 and end2 pointers should just be ignored.  The start1, end1 pointer pair
144            // are sufficient, as during each iteration, start2 and end2 are assigned to start1 and
145            // end1 in the very next step anyways.
146    
147            size += (start1 - last);            // Size of the previous "in-between chunk"
148            size += replaceable.currentSize();  // Size of the next SubSection
149            last = end1;                        // advance the 'last' pointer
150
151        
152            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
153            // Advance the Validity-Checking Pointer Pairs
154            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
155
156            // Advance pointer-pair #1 (but DONT'T do this one the VERY FIRST ITERATION)
157            if (i > 0)
158            {
159                start1  = start2;
160                end1    = end2;
161            }
162
163            // Advance Pointer Pair #2
164            previousReplaceable = replaceable;
165            replaceable         = iter.next();
166            start2              = replaceable.originalLocationStart();
167            end2                = replaceable.originalLocationEnd() - 1;
168
169
170            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
171            // NOW... THE VALIDITY-CHECKING IF-STATEMENTS
172            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
173
174            /*
175            System.out.println(
176                "previousReplaceable: " + previousReplaceable +
177                ", replaceable: " + replaceable + '\n' +
178                "start1: " + start1 + ", end1: " + end1 + ", start2: " + start2 + ", end2: " + end2
179            );
180            */
181
182            if (start2 < start1)
183
184                throw new ReplaceablesUnsortedException(
185                    "'updatedReplaceables' contains at least one Replaceable Element-Pair " +
186                    "which is not sorted from first to last:\n" +
187                    "The " + (i+1) + StringParse.ordinalIndicator(i+1) + " Replaceable returned " +
188                    "by 'updatedReplaceables' starts at page-index " + start1 + '\n' +
189                    "The " + (i+2) + StringParse.ordinalIndicator(i+2) + " Replaceable returned " +
190                    "by 'updatedReplaceables' starts at page-index " + start2,
191                    previousReplaceable, replaceable
192                );
193
194
195            if (    (start2 == start1)  // New section starts at same place as the previous section
196                ||  (start2 <= end1)    // New section begins before the previous section ended
197    
198                // !!! Whenever a user has created a zero-length-replaceable (zero original length)
199                // then the "end" of that replaceable will be "start-1".  Sounds a little silly,
200                // right?  Well inserting a zero-length replaceable happens a lot in JavaDoc
201                // Upgrader.  The two cases of the if-statement are both important.  Remember, the
202                // iterator must be returning sorted elements, or else the previous if statement
203                // would have already failed.
204                )
205
206                throw new ReplaceablesOverlappingException(
207                    "'updatedReplaceables' contains at least one Replaceable Element-Pair " +
208                    "that overlap each-other:\n" +
209                    "The " + (i+1) + StringParse.ordinalIndicator(i+1) + " Replaceable returned " +
210                    "by 'updatedReplaceables' has original-location " +
211                    "[" + start1 + ", " + end1 + "]\n" +
212                    "The " + (i+2) + StringParse.ordinalIndicator(i+2) + " Replaceable returned " +
213                    "by 'updatedReplaceables' has original-location " +
214                    "[" + start2 + ", " + end2 + ']',
215                    previousReplaceable, replaceable
216                );
217
218            if (end1 > SIZE)
219
220                throw new ReplaceableOutOfBoundsException(
221                    "There was a Replaceable Element whose original-location was not within the " +
222                    "bounds of page:\n" +
223                    "The " + (i+1) + StringParse.ordinalIndicator(i+1) + " Replaceable returned " +
224                    "by 'updatedReplaceables' has original-location " +
225                    "[" + start1 + ", " + end1 + "]\n" +
226                    "While page.size() is: " + SIZE,
227                    replaceable
228                );
229
230            i++;
231        }
232
233
234        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
235        // POST-LOOP FINISHING TOUCHES
236        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
237
238        // "Ending Read" Check.  The very last location is not checked, because the loop breaks
239        // before it gets to check pointer-pair-2 (on the last iteration)
240        //
241        // NOTE: The patholigical-cae where there is **ONLY ONE** SubSection in the updatedReplaceables
242        //       input Collection.  If pointer-pair-2 is -1, there is no need to check it... :)
243        //       If (pointer-pair-2 == -1), the loop body was never entered
244
245        if (end2 != -1) if (end2 >= SIZE)
246
247            throw new ReplaceableOutOfBoundsException(
248                "There was a Replaceable Element whose original-location was not within the " +
249                "bounds of page:\n" +
250                "The " + (i+2) + StringParse.ordinalIndicator(i+2) + " Replaceable returned by " +
251                "'updatedReplaceables' has original-location [" + start2 + ", " + end2 + "]\n" +
252                "While page.size() is: " + SIZE,
253                replaceable
254            );
255
256        // the very-last replaceable was not added to the size.
257        size += (start1 - last);            // Size of the previous "in-between chunk"
258        size += replaceable.currentSize();  // Size of the next SubSection
259        last = end1;
260
261
262        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
263        // Build the Return Vector - NOTE - We just computed its final size!
264        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
265        //
266        // ALSO: If the user has requested the DP's be udated, also build the "newSubSections" Vec
267        //
268        // The purpose of the above computation was for instantiating a properly-sized vector
269        // at construction time.  This will save quite a bit of time that would be wasted on
270        // vector resizing.
271
272        Vector<HTMLNode> ret = new Vector<>(size);
273
274
275        // By user request, only!  This really isn't *THAT* important.  All that the
276        // 'newSubSections' Vector will have shall be the exact same-subsections that are passed
277        // as a parameter to this method through the 'updatedReplaceables' parameter - *EXCEPT* that
278        // their SubSection.location fields will be updated to hold the *ACTUAL* / *NEW* locations
279
280        Vector<Replaceable> newReplaceables =
281            updateReplaceablesAfterBuild ? new Vector<>() : null;
282
283
284        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
285        // MAIN-LOOP: Iterate each of the Replaceables that was passed as input to this method.
286        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
287        //
288        // Add their contents to Output-Vector, and make sure to add all "in-between" nodes too!
289
290        // The index-pointer to the **ORIGINAL-VECTOR** (a.k.a. the input vector)
291        int pagePos=0;
292
293
294        // This loop does the replacement.  It is quick and easy if you understand what replacing
295        // a list of subsections involves.
296
297        for (Replaceable r : updatedReplaceables)
298        {
299            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 
300            // Add all of the MOST-RECENT "In-Between Nodes"  (These are all nodes before next SS)
301            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
302            //
303            // AFTERWARDS: Add all nodes in the next Sub-Section 
304
305            // Retrieve all of the 'in-between' nodes
306            while (pagePos < r.originalLocationStart()) ret.add(page.elementAt(pagePos++));
307
308            // Add this Replaceable to the returned output list!
309            r.addAllInto(ret);
310
311            // Skip over the old nodes.
312            pagePos = r.originalLocationEnd(); // don't add one, value is exclusive
313
314
315            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 
316            // User may request that the Sub-Section 'Locations' be updated, rather than discarded
317            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 
318
319            // This is done for convenience so that the user knows where the sections are all
320            // located in the new build.
321            //
322            // NOTE: All this is doing is changing the 'location' field of the old subsection
323            //       which has changed to contain the new 'location'
324
325            if (updateReplaceablesAfterBuild)
326            {
327                int ePos = ret.size();
328                int sPos = ePos - r.currentSize() + 1;
329
330                newReplaceables.add(r.moveAndUpdate(sPos));
331            }
332        }
333
334
335        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
336        // IMPORTANT: Add the last / final Elements that occur *AFTER* the *LAST* Sub-Section
337        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
338        //
339        // This part should also be called "The Tail" of the Page.  (Put the Page-Tail back)
340
341        while (pagePos < page.size()) ret.add(page.elementAt(pagePos++));
342
343
344        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
345        // AGAIN: User may request that Sub-Section 'Locations' be updated, rather than discarded
346        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
347
348        return updateReplaceablesAfterBuild
349            ? new Ret2<>(ret, newReplaceables)
350            : new Ret2<>(ret, null);
351    }
352}