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