001package Torello.Java;
002
003import java.util.Vector;
004import java.util.function.*;
005import java.lang.reflect.Array;
006
007/**
008 * This may be used to check that array have equal lengths - if two or more arrays are expected to
009 * be parallel, but their lengths are not equal, then this exception should be thrown.  This class
010 * also provides multiple <CODE>'check'</CODE> methods that will automatically scan for the
011 * specific error cases, and it will provide consistently worded and formatted error messages to
012 * the invoking code.
013 * 
014 * <BR /><BR />Note that you may also request that the checker look for nulls, and throw a
015 * {@code NullPointerException} if nulls are found.  Furthermore, primitive-arrays may also be 
016 * checked.
017 */
018public class ParallelArrayException extends IllegalArgumentException
019{
020    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUIDEX>  */
021    public static final long serialVersionUID = 1;
022
023    /** Constructs a {@code ParallelArrayException} with no detail message. */
024    public ParallelArrayException()
025    { super(); }
026
027    /**
028     * Constructs an {@code ParallelArrayException} with the specified detail message.
029     * @param message the detail message.
030     */
031    public ParallelArrayException(String message)
032    { super(message); }
033
034    /**
035     * Constructs a new exception with the specified detail {@code 'message'} and 
036     * {@code 'cause'}.
037     * 
038     * <BR /><BR /><B CLASS=JDDescLabel>NOTE:</B>
039     * 
040     * <BR /><BR />The detail message associated with cause is not automatically incorporated into
041     * this exception's detail message.
042     * 
043     * @param message The detail message (which is saved for later retrieval by th
044     * {@code Throwable.getMessage()} method).
045     * 
046     * @param cause the cause (which is saved for later retrieval by the
047     * {@code Throwable.getCause()} method). (A null value is permitted, and indicates that the
048     * cause is nonexistent or unknown.)
049     */
050    public ParallelArrayException(String message, Throwable cause)
051    { super(message); initCause(cause); }
052
053    /**
054     * Constructs a new exception with the specified {@code 'cause'} and a detail message of
055     * {@code (cause==null ? null : cause.toString())} (which typically contains the class
056     * and detail message of cause).  This constructor is useful for exceptions that are little
057     * more than wrappers for other throwables.
058     * 
059     * @param cause The cause (which is saved for later retrieval by the
060     * {@code Throwable.getCause()} method).  (A null value is permitted, and indicates that the
061     * cause is nonexistent or unknown.)
062     */
063    public ParallelArrayException(Throwable cause)
064    { super(); initCause(cause); }
065
066    // If the array itself was a null pointer, this is all you can do
067    private static void NPE(String name)
068    { throw new NullPointerException("Array '" + name + "' was passed a Null Pointer."); }
069
070    // This ensures that the error messages all look the same (same text), and that I don't
071    // retype this many times.
072    private static void CHECK(
073            Object tArr, int tLen, String tName,
074            Object uArr, int uLen, String uName
075        )
076    {
077        if (tLen != uLen) throw new ParallelArrayException(
078            "The length of Array '" + tName + "' (" + tArr.getClass().getSimpleName() + ") " +
079            "is " + tLen + "\n" +
080            "The length of Array '" + uName + "' (" + uArr.getClass().getSimpleName() + ") " +
081            "is " + uLen + "\n" +
082            "Unfortunately, these arrays must be parallel, and thus their lengths must be equal."
083        );
084    }
085
086    /**
087     * This will check that the parameter {@code 'arr1'} (which is presumed to be an array) has
088     * an identical length to that of parameter {@code 'arr2'}.  If these two arrays do not have
089     * the same length, a {@code ParallelArrayException} with throw with an error message.
090     * 
091     * <BR /><BR />Since the purpose of this code is to generate reasonable error messages, without
092     * having to retype this sort of thing of and again, the 'Variable Name' of the arrays are
093     * required as parameters.  The only effects that they have on this code is that they ensure
094     * <I>the output exception messages include those names</I>  (The array names are not part of
095     * the error checking process).
096     * 
097     * <BR /><BR /><B CLASS=JDDescLabel>NOTE:</B>
098     * 
099     * <BR />If for whatever reason, the caller of this method accidentally sends an {@code Object}
100     * to this method which isn't an {@code Array} at all, an exception will throw.  There isn't
101     * really a way to guarantee "Compile Time Checking" for this sort of thing.  Make sure this
102     * method is for checking that <I><B>Array's are Parallel</I></B>.
103     * 
104     * <BR /><BR /><B CLASS=JDDescLabel>FINALLY:</B>
105     * 
106     * <BR />Just about any type of array may be passed - including {@code primitive-arrays}
107     * ({@code int[], float[]}) etc...
108     * 
109     * <BR /><BR />The method {@link StrReplace#r(String, char[], char[])} makes use of this check
110     * as follows:
111     * 
112     * <DIV CLASS=EXAMPLE>{@code 
113     * public static String r(String s, char[] matchChars, char[] replaceChars)
114     * {
115     *      // Check that these arrays have equal lengths, and if not throw the 
116     *      // ParallelArrayException.
117     *      ParallelArrayException.check(matchChars, "matchChars", replaceChars, "replaceChars");
118     *      ...
119     * }
120     * }</DIV>
121     *
122     * @param arr1 This may be any primitive or {@code Object[]} array.
123     * 
124     * @param arr1Name This should be the 'Variable Name' of that array, in your code.  This is
125     * merely used for 'Pretty Printing' purposes.
126     * 
127     * @param arr2 This may be any other primitive or {@code Object[]} array.
128     * 
129     * @param arr2Name This should be the 'Variable Name' of the second array.
130     * 
131     * @throws ParallelArrayException This exception throws if the arrays don't have equal 
132     * lengths.
133     * 
134     * @throws ArrayExpectedError If a non-Array {@code Object} is passed to either of the
135     * Array Parameters.  An {@code 'Error'} is thrown, rather than an {@code 'Exception'} since
136     * the purpose of this check is to identify parallel arrays.  Providing a non-array reference
137     * to this method signals a flaw in the code itself.
138     */
139    public static void check
140        (Object arr1, String arr1Name, Object arr2, String arr2Name)
141    {
142        if (arr1 == null) NPE(arr1Name);
143        if (arr2 == null) NPE(arr2Name);
144
145        if (! arr1.getClass().isArray()) throw new ArrayExpectedError
146            ("Array Parameter '" + arr1Name + "' is not an array.");
147
148        if (! arr2.getClass().isArray()) throw new ArrayExpectedError
149            ("Array Parameter '" + arr2Name + "' is not an array.");
150        
151        CHECK(
152            arr1, java.lang.reflect.Array.getLength(arr1), arr1Name,
153            arr2, java.lang.reflect.Array.getLength(arr2), arr2Name
154        );
155    }
156
157    /**
158     * This will check that the parameter {@code 'tArr'} has an identical length to that of
159     * parameter {@code 'arr'} (which is presumed to be an array).  If these two arrays do not have
160     * the same length, a {@code ParallelArrayException} with throw with an error message.
161     * 
162     * <BR /><BR />This method differs from the previous
163     * {@link #check(Object, String, Object, String)}, in that the Variable-Type Parameter
164     * {@code '<T>'} guarantees that at least one of the parameters must be an array.  This 
165     * facilitates another check - that for nulls in the array.  It may or may not be desired to
166     * check for the prescense of {@code 'null'}, but if it is that can be requested by passing 
167     * {@code 'TRUE'} to the parameter {@code 'throwIfHasNulls'}.
168     * 
169     * <BR /><BR />The {@link StrReplace#r(boolean, String, String[], char[])} utilizes this method
170     * as below:
171     * 
172     * <DIV CLASS=EXAMPLE>{@code 
173     * public static String r
174     *      (boolean ignoreCase, String s, String[] matchStrs, char[] replaceChars)
175     * {
176     *      // Make sure the 'matchStrs' array is parallel to 'replaceChars', and also make sure
177     *      // 'matchStr' does not have null elements.  If not, throw ParallelArrayException
178     *      ParallelArrayException.check
179     *          (matchStrs, "matchStrs", true, replaceChars, "replaceChars");
180     *      ...
181     * }
182     * }</DIV>
183     *
184     * @param <T> This type-parameter is merely being utilized to allow <I>any array type</I>
185     * to be received by this method.  It is nothing more than a place-holder (similar to the
186     * {@code '?'} for generic classes).
187     * 
188     * @param tArr This may be any {@code Object[]} instance array.
189     * 
190     * @param tName This should be the 'Variable Name' of that array, in your code.  This is
191     * merely used for 'Pretty Printing' purposes.
192     * 
193     * @param throwIfHasNulls This parameter requests to check for the presence of a {@code 'null'}
194     * inside the {@code 'tArr'} array, and will throw a {@code NullPointerException} is one is
195     * found.
196     * 
197     * @param arr This may be any primitive or {@code Object[]} array.
198     * 
199     * @param arrName This should be the 'Variable Name' of the second array.
200     * 
201     * @throws ParallelArrayException This exception throws if the arrays don't have equal 
202     * lengths.
203     * 
204     * @throws NullPointerException This exception throws if the {@code 'tArr'} contains any
205     * {@code 'null'} values.
206     * 
207     * @throws ArrayExpectedError If a non-Array {@code Object} is passed to either of the
208     * Array Parameters.  An {@code 'Error'} is thrown, rather than an {@code 'Exception'} since
209     * the purpose of this check is to identify parallel arrays.  Providing a non-array reference
210     * to this method signals a flaw in the code itself.
211     */
212    public static <T> void check(
213            T[] tArr, String tName, boolean throwIfHasNulls,
214            Object arr, String arrName
215        )
216    {
217        if (tArr == null)   NPE(tName);
218        if (arr == null)    NPE(arrName);
219
220        if (! arr.getClass().isArray()) throw new ArrayExpectedError
221            ("Array Parameter '" + arrName + "' is not an array.");
222
223        CHECK(tArr, tArr.length, tName, arr, Array.getLength(arr), arrName);
224
225        if (throwIfHasNulls)
226            for (int i=0; i < tArr.length; i++)
227                if (tArr[i] == null)
228                    throw new NullPointerException(
229                        tName + '[' + i + "] (" + tArr.getClass().getSimpleName() + ") " +
230                        "contains a null reference."
231                    );
232    }
233
234    /**
235     * This will check that the parameter {@code 'tArr'} has an identical length to that of
236     * parameter {@code 'arr'} (which is presumed to be an array).  If these two arrays do not have
237     * the same length, a {@code ParallelArrayException} with throw with an error message.
238     * 
239     * <BR /><BR />This method differs from the previous
240     * {@link #check(Object, String, Object, String)}, in that using Variable-Type Parameters
241     * ({@code '<T>'} and {@code '<U>'}) guarantee that both parameters are arrays.  The purpose 
242     * here is that it facilitates another check - that for the presence of {@code 'nulls'}.  It 
243     * may or may not be desired to check for the prescense of {@code 'null'} within the arrays,
244     * but if it is, simply pass {@code 'TRUE'} to either of the {@code 'throwIfHasNulls'}
245     * parameters, and the corresponding array will be checked.
246     * 
247     * <BR /><BR />This check is used by {@link StrReplace#r(boolean, String, String[], String[])}
248     * as below:
249     * 
250     * <DIV CLASS=EXAMPLE>{@code 
251     * public static String r
252     *      (boolean ignoreCase, String s, String[] matchStrs, String[] replaceStrs)
253     * {
254     *      // Make sure these arrays are parallel, and if not throw ParallelArrayException
255     *      // If there are any 'null' values in these arrays, throw NullPointerException
256     *      ParallelArrayException.check
257     *          (matchStrs, "matchStrs", true, replaceStrs, "replaceStrs", true);
258     *      ...
259     * }
260     * }</DIV>
261     *
262     * @param <T> This type-parameter is merely being utilized to allow <I>any array type</I>
263     * to be received by this method.  It is nothing more than a place-holder (similar to the
264     * {@code '?'} for generic classes).
265     *
266     * @param <U> This type-parameter is merely being utilized to allow <I>any array type</I>
267     * to be received by this method.  It is nothing more than a place-holder (similar to the
268     * {@code '?'} for generic classes).
269     *
270     * @param tArr This may be any {@code Object[]} instance array.
271     * 
272     * @param tName This should be the 'Variable Name' of that array, in your code.  This is
273     * merely used for 'Pretty Printing' purposes.
274     * 
275     * @param throwIfTHasNulls This parameter requests to check for the presence of a
276     * {@code 'null'} inside the {@code 'tArr'} array, and will throw a
277     * {@code NullPointerException} is one is found.
278     *
279     * @param uArr This may be any {@code Object[]} instance array.
280     * 
281     * @param uName This should be the 'Variable Name' of that array, in your code.  This is
282     * merely used for 'Pretty Printing' purposes.
283     * 
284     * @param throwIfUHasNulls This parameter requests to check for the presence of a
285     * {@code 'null'} inside the {@code 'uArr'} array, and will throw a
286     * {@code NullPointerException} is one is found.
287     * 
288     * @throws ParallelArrayException This exception throws if the arrays don't have equal 
289     * lengths.
290     * 
291     * @throws NullPointerException This exception throws if the {@code 'tArr'} contains any
292     * {@code 'null'} values.
293     */
294    public static <T, U> void check(
295            T[] tArr, String tName, boolean throwIfTHasNulls,
296            U[] uArr, String uName, boolean throwIfUHasNulls
297        )
298    {
299        if (tArr == null) NPE(tName);
300        if (uArr == null) NPE(uName);
301
302        CHECK(tArr, tArr.length, tName, uArr, uArr.length, uName);
303
304        if (throwIfTHasNulls)
305            for (int i=0; i < tArr.length; i++)
306                if (tArr[i] == null)
307                    throw new NullPointerException(
308                        tName + '[' + i + "] (" + tArr.getClass().getSimpleName() + ") " +
309                        "contains a null reference."
310                    );
311
312        if (throwIfUHasNulls)
313            for (int i=0; i < uArr.length; i++)
314                if (uArr[i] == null)
315                    throw new NullPointerException(
316                        uName + "[" + i + "] (" + uArr.getClass().getSimpleName() + ") " +
317                        "contains a null reference."
318                    );
319    }
320}