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