001package Torello.Java;
002
003import java.util.*;
004import Torello.HTML.*;
005
006/**
007 * The Loop-Variable End-Points class is used extensively throughout the Java-HTML Library for
008 * throwing properly formatted exception messages <I>vis-a-vis</I> loop variables.
009 * 
010 * <EMBED CLASS='external-html' DATA-FILE-ID=LV>
011 */
012public class LV implements java.io.Serializable, Cloneable
013{
014    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */
015    public static final long serialVersionUID = 1;
016
017    /**
018     * This integer represents the starting point of a {@code for-loop}.  It is guaranteed to be
019     * consistent with the {@code Vector} that was used with the constructor of this class.
020     */
021    public final int start;
022
023    /**
024     * This integer represents the ending point of a {@code for-loop}.  It is guaranteed to be
025     * consistent with the {@code Vector} that was used with the constructor of this class.
026     */
027    public final int end;
028
029
030    // ********************************************************************************************
031    // Standard Java Methods
032    // ********************************************************************************************
033
034
035    /**
036     * Implements the standard java {@code 'hashCode()'} method.  This will provide a hash-code 
037     * that is very likely to avoid crashes.
038     * @return A hash-code that may be used for inserting {@code 'this'} instance into a hashed
039     * table, map or list.
040     */
041    public int hashCode()
042    { return this.start + (1000 * this.end); }
043
044    /**
045     * Java's {@code toString()} requirement.
046     * @return A string representing 'this' instance of LV / Loop-Variables.
047     */
048    public String toString() { return "[Loop-Start: " + start + ", Loop-Break: " + end + "]"; }
049
050    /**
051     * Java's {@code public boolean equals(Object o)} requirements.
052     *
053     * @param o This may be any Java Object, but only ones of {@code 'this'} type whose
054     * internal-values are identical with {@code 'this'} instance will make this method return
055     * {@code TRUE}.
056     *
057     * @return {@code TRUE} if (and only if) parameter {@code 'o'} is an {@code instanceof LV} and,
058     * also, has equal {@code 'start'} and {@code 'end'} field values.
059     */
060    public boolean equals(Object o)
061    {
062        if (o instanceof LV)
063        {
064            LV dp = (LV) o;
065            return (this.start == dp.start) && (this.end == dp.end);
066        }
067
068        else return false;
069    }
070
071    /**
072     * Java's {@code interface Cloneable} requirements.  This instantiates a new {@code LV} with
073     * identical {@code 'start', 'end'} fields.
074     * 
075     * @return A new {@code LV} instance whose internal fields are identical to this one.
076     */
077    public LV clone() { return new LV(this.start, this.end); }
078
079    /**
080     * Returns the number of elements that would be iterated, if using {@code 'this'} instance of
081     * {@code LV} as a loop-control variable.
082     * 
083     * @return The number of element's that are referenced by {@code 'this'} instance.
084     * 
085     * @see #start
086     * @see #end
087     */
088    public int size() { return end - start; }
089
090    // ********************************************************************************************
091    // ********************************************************************************************
092    // Internal Helper Methods
093    // ********************************************************************************************
094    // ********************************************************************************************
095
096
097    // Private "Clone Constructor"
098    private LV(int start, int end) { this.start=start; this.end=end; }
099
100    private String NOTEV (int size, int sPos, int ePos)
101    {
102        return
103            "Vector.size(): [" + size + "], " +
104            "Start-Position: [" + sPos + "], " +
105            "End-Position:[" + ePos + ']';
106    }
107
108    private String NOTESTR(int length, int sPos, int ePos)
109    {
110        return
111            "String.length(): [" + length + "], " +
112            "sPos: [" + sPos + "], " +
113            "ePos: [" + ePos + ']';
114    }
115
116    private String NOTESTR(int length, int sPos, int ePos, int cmprStrLen)
117    {
118        return
119            "String.length(): [" + length + "], " +
120            "sPos: [" + sPos + "], " +
121            "ePos: [" + ePos + "], " +
122            "cmprStrLen: [" + cmprStrLen + ']';
123    }
124
125    private String NOTEA(int length, int sPos, int ePos)
126    {
127        return
128            "Array.length: [" + length + "], " +
129            "Start-Position: [" + sPos + "], " +
130            "End-Position:[" + ePos + ']';
131    }
132
133
134    // ********************************************************************************************
135    // ********************************************************************************************
136    // Constructors
137    // ********************************************************************************************
138    // ********************************************************************************************
139
140
141    /**
142     * Checks input parameters and either throws {@code IndexOutOfBoundsException} or returns
143     * proper loop-variable starting &amp; ending values.
144     *
145     * <EMBED CLASS='external-html' DATA-FILE-ID=LV_IMPT_NOTE>
146     *
147     * @param html This is any vectorized-html page {@code Vector}.
148     *
149     * @param sPos This is the starting position in the {@code Vector} for the loop-variable 
150     * counter being created.  This value is <B>inclusive</B>.  This means that the first element
151     * searched by {@code 'this'} loop-variable counter instance <I>shall be at the index 
152     * {@code 'sPos'}</I>.
153     *
154     * <BR /><BR />If all validity checks are passed, {@code 'this.start'} is assigned the value of
155     * {@code 'sPos'}.
156     *
157     * @param ePos This is the ending position in the {@code Vector} for the loop-variable counter
158     * being created.  This value is <B>exclusive</B>.  This means that the last element searched
159     * by {@code 'this'} loop-variable counter instance <I>shall be at the index 
160     * {@code ePos - 1}</I>.
161     *
162     * <BR /><BR />A negative value may be passed to {@code 'ePos'} - and if so, {@code this.end}
163     * shall be set to {@code html.size()}, otherwise {@code this.end} is assigned the value of
164     * {@code 'ePos'}.
165     *
166     * @throws IndexOutOfBoundsException <EMBED CLASS='external-html' DATA-FILE-ID=VIOOBEX>
167     */
168    public LV(Vector<? extends HTMLNode> html, int sPos, int ePos)
169    {
170        int size = html.size();
171
172        if ((size == 0) && (sPos == 0) && (ePos <= 0))
173        { this.start = this.end = 0; return; }
174
175        if (sPos >= size) throw new IndexOutOfBoundsException(
176            "Starting Vector Position is greater than or equal to the Vector's size:\n" +
177            NOTEV(size, sPos, ePos)
178        );
179
180        if (sPos < 0) throw new IndexOutOfBoundsException
181            ("Starting Vector Position is negative: " + NOTEV(size, sPos, ePos));
182
183        if (ePos > size) throw new IndexOutOfBoundsException(
184            "Ending Vector Position is greater than the size of the Vector:\n" +
185            NOTEV(size, sPos, ePos)
186        );
187
188        if (ePos == 0) throw new IndexOutOfBoundsException
189            ("Ending Vector Position is zero.:\n" + NOTEV(size, sPos, ePos));
190
191        this.start  = sPos;
192        this.end    = (ePos <= 0) ? size : ePos;
193
194        if (start > end) throw new IllegalArgumentException(
195            "The starting and ending Vector Positions are not properly chosen:\n" + 
196            NOTEV(size, sPos, ePos)
197        );
198    }
199
200
201    /**
202     * Explaining the issue of type-checking with java-generics, once a certain point has been
203     * reached, is an exercise in futility.  The JDK development team did a lot of work on Java
204     * Generics, but didn't not bring them into the "Run-Time" world.  As such, there are a few,
205     * details, as we shall call them with names like "CAP#1" that prevent some perfectly
206     * reasonable looking code structures that simply will not compile.
207     * 
208     * <BR /><BR />This constructor is identical to the other constructor in this class, but has
209     * had its parameter position inputs reversed in the method signature.  Also, <I><B>it accepts
210     * a raw-type {@code Vector} instance.</I></B>  This should not present a problem to users at
211     * all, but to the developer of this project / package, it can be disconcerting.  In any case,
212     * this constructor checks the input parameters and either throws
213     * {@code IndexOutOfBoundsException} or returns a proper loop-variable starting-ending point
214     * class-object.
215     *
216     * <EMBED CLASS='external-html' DATA-FILE-ID=LV_IMPT_NOTE>
217     * 
218     * @param v This may be any {@code raw-type Vector}.
219     * <EMBED CLASS='external-html' DATA-FILE-ID=RAWTYPES>
220     *
221     * @param sPos This is the starting position in the {@code Vector} for the loop-variable 
222     * counter being created.  This value is <B>inclusive</B>.  This means that the first element
223     * searched by {@code 'this'} loop-variable counter instance <I>shall be at the index 
224     * {@code 'sPos'}</I>.
225     *
226     * <BR /><BR />If all validity checks are passed, {@code 'this.start'} is assigned the value of
227     * {@code 'sPos'}.
228     *
229     * @param ePos This is the ending position in the {@code Vector} for the loop-variable counter
230     * being created.  This value is <B>exclusive</B>.  This means that the last element searched
231     * by {@code 'this'} loop-variable counter instance <I>shall be at the index 
232     * {@code ePos - 1}</I>.
233     *
234     * <BR /><BR />A negative value may be passed to {@code 'ePos'} - and if so, {@code this.end}
235     * shall be set to {@code html.size()}, otherwise {@code this.end} is assigned the value of
236     * {@code 'ePos'}.
237     *
238     * @throws IndexOutOfBoundsException <EMBED CLASS='external-html' DATA-FILE-ID=VIOOBEX>
239     */
240    public LV(int sPos, int ePos, Vector<?> v)
241    {
242        int size = v.size();
243
244        if ((size == 0) && (sPos == 0) && (ePos <= 0))
245        { this.start = this.end = 0; return; }
246
247        if (sPos >= size) throw new IndexOutOfBoundsException(
248            "Starting Vector Position is greater than or equal to the Vector's size:\n" +
249            NOTEV(size, sPos, ePos)
250        );
251
252        if (sPos < 0) throw new IndexOutOfBoundsException
253            ("Starting Vector Position is negative: " + NOTEV(size, sPos, ePos));
254
255        if (ePos > size) throw new IndexOutOfBoundsException(
256            "Ending Vector Position is greater than the size of the Vector:\n" +
257            NOTEV(size, sPos, ePos)
258        );
259
260        if (ePos == 0) throw new IndexOutOfBoundsException
261            ("Ending Vector Position is zero.:\n" + NOTEV(size, sPos, ePos));
262
263        this.start  = sPos;
264        this.end    = (ePos <= 0) ? size : ePos;
265
266        if (start > end) throw new IllegalArgumentException(
267            "The starting and ending Vector Positions are not properly chosen:\n" +
268            NOTEV(size, sPos, ePos)
269        );
270    }
271
272    /**
273     * Checks input parameters and either throws {@code StringIndexOutOfBoundsException} or returns
274     * proper loop-variable starting &amp; ending values.
275     *
276     * <EMBED CLASS='external-html' DATA-FILE-ID=LV_IMPT_NOTE>
277     *
278     * @param s This may be any {@code String}.
279     *
280     * @param sPos This is the starting position in the {@code String} for the loop-variable 
281     * counter being created.  This value is <B>inclusive</B>.  This means that the first element
282     * searched by {@code 'this'} loop-variable counter instance <I>shall be at the index 
283     * {@code 'sPos'}</I>.
284     *
285     * <BR /><BR />If all validity checks are passed, {@code 'this.start'} is assigned the value of
286     * {@code 'sPos'}.
287     *
288     * @param ePos This is the ending position in the {@code String} for the loop-variable counter
289     * being created.  This value is <B>exclusive</B>.  This means that the last element searched
290     * by {@code 'this'} loop-variable counter instance <I>shall be at the index 
291     * {@code ePos - 1}</I>.
292     *
293     * <BR /><BR />A negative value may be passed to {@code 'ePos'} - and if so, {@code this.end}
294     * shall be set to {@code s.size()}, otherwise {@code this.end} is assigned the value of
295     * {@code 'ePos'}.
296     *
297     * @throws StringIndexOutOfBoundsException <EMBED CLASS='external-html' DATA-FILE-ID=SIOOB_EX>
298     */
299    public LV(String s, int sPos, int ePos)
300    {
301        int length = s.length();
302
303        if ((length == 0) && (sPos == 0) && (ePos <= 0))
304        { this.start = this.end = 0; return; }
305
306        if (sPos >= length) throw new StringIndexOutOfBoundsException(
307            "Starting String Position is greater than or equal to the String's length:\n" +
308            NOTESTR(length, sPos, ePos)
309        );
310
311        if (sPos < 0) throw new StringIndexOutOfBoundsException
312            ("Starting String Position is negative:\n" + NOTESTR(length, sPos, ePos));
313
314        if (ePos > length) throw new StringIndexOutOfBoundsException(
315            "Ending String Position is greater than the length of the String:\n" +
316            NOTESTR(length, sPos, ePos)
317        );
318
319        if (ePos == 0) throw new StringIndexOutOfBoundsException
320            ("Ending String Position is zero:\n" + NOTESTR(length, sPos, ePos));
321
322        this.start  = sPos;
323        this.end    = (ePos <= 0) ? length : ePos;
324
325        if (start > end) throw new IllegalArgumentException(
326            "The starting and ending String positions are not properly chosen:\n" +
327            NOTESTR(length, sPos, ePos)
328        );
329    }
330
331    /**
332     * Checks input parameters and either throws {@code StringIndexOutOfBoundsException} or returns
333     * proper loop-variable starting &amp; ending values.  In this constructor, the length of a
334     * second, comparing-{@code String}, substring is expected as a parameter.  This version of the
335     * {@code LV} constructor is used by {@code class StrIndexOf}.
336     *
337     * <EMBED CLASS='external-html' DATA-FILE-ID=LV_IMPT_NOTE>
338     *
339     * @param s This may be any {@code String}.
340     *
341     * @param sPos This is the starting position in the {@code String} for the loop-variable 
342     * counter being created.  This value is <B>inclusive</B>.  This means that the first element
343     * searched by {@code 'this'} loop-variable counter instance <I>shall be at the index 
344     * {@code 'sPos'}</I>.
345     *
346     * <BR /><BR />If all validity checks are passed, {@code 'this.start'} is assigned the value of
347     * {@code 'sPos'}.
348     *
349     * @param ePos This is the ending position in the {@code String} for the loop-variable counter
350     * being created.  This value is <B>exclusive</B>.  This means that the last element searched
351     * by {@code 'this'} loop-variable counter instance <I>shall be at the index 
352     * {@code ePos - 1}</I>.
353     *
354     * <BR /><BR />A negative value may be passed to {@code 'ePos'} - and if so, {@code this.end}
355     * shall be set to {@code s.length() - cmprStrLen + 1}, otherwise {@code this.end} is assigned
356     * the value of {@code 'ePos'}.
357     *
358     * <BR /><BR /><B><SPAN STYLE="color: red;">MEANING:</B></SPAN> Since the {@code String}-Search
359     * and {@code String-Loops} should be as optimized as possible - due to the fact there is a
360     * possibility they could be invoked many, many times - Setting the value of {@code this.end}
361     * to be 'less the value of a compare-{@code String} length' means that many fewer comparison's 
362     * need to be performed.  The compare-{@code String} cannot possibly fit between {@code ePos} 
363     * and 'the end of the source-{@code String}' if {@code ePos} is closer to the end of the 
364     * source-{@code String} than the total size of {@code 'cmprStrLen'}.  Primarily, if this does
365     * not make sense, this constructor is an optimization on the standard {@code String} loop
366     * variable constructor that allows to shorted {@code this.end} in order to eliminate
367     * extraneous {@code for-loop} comparison's in {@code class StrCmpr}.
368     *
369     * @param cmprStrLen This is just an integer that represents the length of a comparison
370     * {@code String}.  When looping through the contents of one {@code String}, and comparing
371     * those contents to another {@code String} - <I><B>the length of that second
372     * {@code String}</I></B> should be subtracted from the value that is stored in the field
373     * {@code public final int end}  This is because one {@code String} cannot be a substring of
374     * another with a beginning matching index that does not accommodate a match before the
375     * {@code String}, itself, runs out.
376     * @throws StringIndexOutOfBoundsException <EMBED CLASS='external-html' DATA-FILE-ID=SIOOB_EX>
377     */
378    public LV(String s, int sPos, int ePos, int cmprStrLen)
379    {
380        int length         = s.length();
381
382        if ((length == 0) && (sPos == 0) && (ePos <= 0))
383        { this.start = this.end = 0; return; }
384
385         if (sPos >= length) throw new StringIndexOutOfBoundsException(
386             "Starting String Position is greater than or equal to the String's length:\n"  +
387            NOTESTR(length, sPos, ePos, cmprStrLen)
388        );
389
390        if (sPos < 0) throw new StringIndexOutOfBoundsException
391            ("Starting String position is negative:\n" + NOTESTR(length, sPos, ePos, cmprStrLen));
392
393        if (ePos > length) throw new StringIndexOutOfBoundsException(
394            "Ending String Position is greater than the length of the String:\n" +
395            NOTESTR(length, sPos, ePos, cmprStrLen)
396        );
397
398        if (ePos == 0) throw new StringIndexOutOfBoundsException
399            ("Ending String Position is zero:\n" + NOTESTR(length, sPos, ePos, cmprStrLen));
400
401        this.start  = sPos;
402        int endTEMP = (ePos <= 0) ? length : ePos;
403
404        if (start > endTEMP) throw new IllegalArgumentException(
405            "The starting and ending String positions are not properly chosen:\n" +
406            NOTESTR(length, sPos, ePos, cmprStrLen)
407        );
408
409        endTEMP     = endTEMP - cmprStrLen + 1;
410        this.end    = (endTEMP < sPos) ? sPos : endTEMP;
411    }
412
413
414    /**
415     * Checks input parameters and either throws {@code ArrayIndexOutOfBoundsException} or returns
416     * proper loop-variable starting &amp; ending values.
417     * 
418     * <EMBED CLASS='external-html' DATA-FILE-ID=LV_IMPT_NOTE>
419     *
420     * @param arr This may be an array of any type {@code Object}
421     *
422     * @param sPos This is the starting position in the {@code 'array'} for the loop-variable 
423     * counter being created.  This value is <B>inclusive</B>.  This means that the first element
424     * searched by {@code 'this'} loop-variable counter instance <I>shall be at the index 
425     * {@code 'sPos'}</I>.
426     *
427     * <BR /><BR />If all validity checks are passed, {@code 'this.start'} is assigned the value of
428     * {@code 'sPos'}.
429     *
430     * @param ePos This is the ending position in the {@code 'array'} for the loop-variable counter
431     * being created.  This value is <B>exclusive</B>.  This means that the last element searched
432     * by {@code 'this'} loop-variable counter instance <I>shall be at the index 
433     * {@code ePos - 1}</I>.
434     *
435     * <BR /><BR />A negative value may be passed to {@code 'ePos'} - and if so, {@code this.end}
436     * shall be set to {@code array.length}, otherwise {@code this.end} is assigned the value of
437     * {@code 'ePos'}.
438     * 
439     * @throws ArrayIndexOutOfBoundsException <EMBED CLASS='external-html' DATA-FILE-ID=AIOOB_EX>
440     */
441    public <T> LV(T[] arr, int sPos, int ePos)
442    {
443        int length = arr.length;
444
445        if ((length == 0) && (sPos == 0) && (ePos <= 0))
446        { this.start = this.end = 0; return; }
447
448        if (sPos >= length) throw new ArrayIndexOutOfBoundsException(
449            "Starting Array Position is greater than or equal to the Array's length:\n" +
450            NOTEA(length, sPos, ePos)
451        );
452
453        if (sPos < 0) throw new ArrayIndexOutOfBoundsException
454            ("Starting Array Position is negative: " + NOTEA(length, sPos, ePos));
455
456        if (ePos > length) throw new IndexOutOfBoundsException(
457            "Ending Array Position is greater than the length of the Array:\n" +
458            NOTEA(length, sPos, ePos)
459        );
460
461        if (ePos == 0) throw new ArrayIndexOutOfBoundsException
462            ("Ending Array Position is zero.:\n" + NOTEA(length, sPos, ePos));
463
464        this.start  = sPos;
465        this.end    = (ePos <= 0) ? length : ePos;
466
467        if (start > end) throw new IllegalArgumentException(
468            "The starting and ending Array Positions are not properly chosen:\n" +
469            NOTEA(length, sPos, ePos)
470        );
471    }
472
473    /**
474     * Checks input parameters and either throws {@code ArrayIndexOutOfBoundsException} or returns
475     * proper loop-variable starting &amp; ending values.
476     *
477     * <EMBED CLASS='external-html' DATA-FILE-ID=LV_IMPT_NOTE>
478     * 
479     * <BR /><BR />In this particular method, when {@code 'ePos'} is passed a Negative-Value, the
480     * length of the input-array parameter {@code 'primitiveArray'} (the value assigned to
481     * {@link #end}) is computed using a heuristic from {@code java.lang.reflect}.
482     *
483     * @param primitiveArray This may be an array of any <B>primitive</B> type. {@code int[],
484     * float[], boolean[]}, etc...
485     *
486     * @param sPos This is the starting position in the {@code 'array'} for the loop-variable 
487     * counter being created.  This value is <B>inclusive</B>.  This means that the first element
488     * searched by {@code 'this'} loop-variable counter instance <I>shall be at the index 
489     * {@code 'sPos'}</I>.
490     *
491     * <BR /><BR />If all validity checks are passed, {@code 'this.start'} is assigned the value of
492     * {@code 'sPos'}.
493     *
494     * @param ePos This is the ending position in the {@code 'array'} for the loop-variable counter
495     * being created.  This value is <B>exclusive</B>.  This means that the last element searched
496     * by {@code 'this'} loop-variable counter instance <I>shall be at the index 
497     * {@code ePos - 1}</I>.
498     *
499     * <BR /><BR />A negative value may be passed to {@code 'ePos'} - and if so, {@code this.end}
500     * shall be set to {@code primitiveArray.length}, otherwise {@code this.end} is assigned the
501     * value of {@code 'ePos'}.
502     *
503     * @throws ArrayIndexOutOfBoundsException
504     * <EMBED CLASS='external-html' DATA-FILE-ID=AIOOB_EX>
505     * 
506     * @throws ArrayExpectedError This error is thrown if the reference passed to parameter
507     * {@code 'primitiveArray'} is not actually a reference to a {@code byte[], short[], int[]}
508     * etc... primitive array.  An error is used because the whole purpose of the class {@code LV}
509     * is to help reduce programming errors with automatic for-loop bounds checking.  If, in the
510     * course of exception checking, another exception is thrown it signals a more fundamental
511     * mistake has been made.
512     */
513    public LV(int sPos, int ePos, Object primitiveArray)
514    {
515        if (! primitiveArray.getClass().isArray()) throw new ArrayExpectedError(
516            "The Object passed to 'primitiveArray' is not an actually an array, but " +
517            "rather an instance of [" + primitiveArray.getClass().getName() + ']'
518        );
519
520        int length = java.lang.reflect.Array.getLength(primitiveArray);
521
522        if ((length == 0) && (sPos == 0) && (ePos <= 0))
523        { this.start = this.end = 0; return; }
524
525        if (sPos >= length) throw new ArrayIndexOutOfBoundsException(
526            "Starting Array Position is greater than or equal to the Array's length:\n" +
527            NOTEA(length, sPos, ePos)
528        );
529
530        if (sPos < 0) throw new ArrayIndexOutOfBoundsException
531            ("Starting Array Position is negative: " + NOTEA(length, sPos, ePos));
532
533        if (ePos > length) throw new ArrayIndexOutOfBoundsException(
534            "Ending Array Position is greater than the length of the Array:\n" +
535            NOTEA(length, sPos, ePos)
536        );
537
538        if (ePos == 0) throw new ArrayIndexOutOfBoundsException
539            ("Ending Array Position is zero.:\n" + NOTEA(length, sPos, ePos));
540
541        this.start  = sPos;
542        this.end    = (ePos <= 0) ? length : ePos;
543
544        if (start > end) throw new IllegalArgumentException(
545            "The starting and ending Array Positions are not properly chosen:\n" +
546            NOTEA(length, sPos, ePos)
547        );
548    }
549}