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