001package Torello.Java;
002
003import static Torello.Java.C.*;
004
005import java.lang.reflect.InvocationTargetException;
006import java.util.Arrays;
007
008/**
009 * <B><CODE>'Exception Cause Chain'</CODE></B> helps convert exception messages whose
010 * <CODE><B>Throwable.cause()</B></CODE> method returns a non-null <CODE>cause</CODE>, thereby
011 * unrolling <I>(and printing)</I> this chain of exceptions into a readable <CODE>String</CODE>.
012 * 
013 * <EMBED CLASS=external-html DATA-FILE-ID=EXCC>
014 */
015@Torello.JavaDoc.Annotations.StaticFunctional
016public class EXCC
017{
018    private EXCC() { }
019
020
021    // ********************************************************************************************
022    // ********************************************************************************************
023    // Print the Cause Chain
024    // ********************************************************************************************
025    // ********************************************************************************************
026
027
028    /**
029     * Convenience Method.
030     * <BR />Invokes: {@link #toString(Throwable)}
031     * <BR />And-Then: {@link StrIndent#indentTabs(String, int)}
032     */
033    public static String toString(Throwable t, int indentation)
034    { return StrIndent.indentTabs(toString(t), indentation); }
035
036    /**
037     * Prints the {@code Exception}-message and {@code Exception}-stack trace to an output
038     * {@code String}, and invokes, recursively, this method with any cause-{@code Throwable's}
039     * that are present in this {@code Throwable}.
040     *
041     * @param t Any Java {@code Throwable}.  Java {@code Throwable's} with one or {@code 'cause'}
042     * {@code Throwable's} will utilize more fully the printing-features of this class,
043     * {@code 'EXCC'} - <I>though any {@code Throwable} will be properly printed</I>.
044     * 
045     * @return This method doesn't actually print anything to the screen or terminal, it just
046     * returns a {@code String} that you may print yourself - <I>or write to a
047     * {@code class StringBuilder}, or any variation of text-output you wish.</I>
048     */
049    public static String toString(Throwable t)
050    {
051        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
052        // Initialize the Variables
053        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
054
055        final StackTraceElement[]   steArr          = t.getStackTrace();
056        final StringBuilder         sb              = new StringBuilder();
057        final Throwable             cause           = t.getCause();
058        final Throwable             legacyCause     = legacyCause(t);
059        final boolean               hasCause        = (cause != null);
060        final boolean               hasLegacyCause  = (legacyCause != null) && (legacyCause != cause);
061
062        String msg          = t.getMessage();
063        String localMsg     = t.getLocalizedMessage();
064        String legacyMsg    = (hasLegacyCause) ? legacyMsg(t) : null;
065
066        final boolean hasMessage    = (msg != null)         && (msg.length() > 0);
067        final boolean hasLocalMsg   = (localMsg != null)    && (localMsg.length() > 0);
068
069        if (hasMessage)     msg         = StrIndent.indent(msg, 1, false, true);
070        if (hasLocalMsg)    localMsg    = StrIndent.indent(localMsg, 1, false, true);
071
072
073        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
074        // Print the "Top Part" / "Header Part" / Error-Message to the Return-Output StringBuilder
075        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
076
077        sb.append(BRED + "THROWN: " + t.getClass().getCanonicalName() + RESET + "\n");
078
079        if (hasMessage)
080        {
081            sb.append(BCYAN + "Throwable.getMessage():\n" + RESET + msg + "\n");
082
083            if (hasLocalMsg && (! msg.equals(localMsg)))
084                sb.append(BCYAN + "Throwable.getLocalizedMessage():\n" + RESET + localMsg + "\n");
085        }
086
087        else if (hasLocalMsg)
088            sb.append(BCYAN + "Throwable.getLocalizedMessage():\n" + RESET + localMsg + "\n");
089
090        else
091            sb.append(BYELLOW + "No Exception Messages Provided.\n" + RESET);
092
093
094        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
095        // Print the Stack-Trace
096        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
097
098        sb.append(BCYAN + "StackTrace:\n" + RESET);
099
100        int temp, maxLN = 0;
101
102        for (StackTraceElement ste : steArr)
103            if ((temp = ste.getLineNumber()) > maxLN)
104                maxLN = temp;
105
106        final int base10 =
107            2 + // 2: colon + space
108            ((int) Math.ceil(Math.log10(maxLN)));
109
110        for (int k=0; k < steArr.length; k++) sb.append(
111            '\t' + StringParse.rightSpacePad(steArr[k].getLineNumber() + ":", base10) +
112            steArr[k].getClassName() + "." +
113            steArr[k].getMethodName() + "()\n"
114        );
115
116
117        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
118        // Build the StringBuilder & Return / Exit
119        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
120
121        return
122            sb.toString() +
123            (hasCause       ? StrIndent.indentTabs(toString(cause), 1)                      : "") +
124            (hasLegacyCause ? (legacyMsg + StrIndent.indentTabs(toString(legacyCause), 1))  : "");
125    }
126
127
128    // ********************************************************************************************
129    // ********************************************************************************************
130    // Variant: Abbreviated Cause-Chain Printing
131    // ********************************************************************************************
132    // ********************************************************************************************
133
134
135    /**
136     * Convenience Method.
137     * <BR />Invokes: {@link #toStringMaxTraces(Throwable, int)}
138     * <BR />And-Then: {@link StrIndent#indentTabs(String, int)}
139     */
140    public static String toStringMaxTraces(Throwable t, int maxNumInvocations, int indentation)
141    { return StrIndent.indent(toStringMaxTraces(t, maxNumInvocations), indentation); }
142
143    /**
144     * Prints the {@code Exception}-message and {@code Exception}-stack trace to an output
145     * {@code String}, and invokes, recursively, this method with any cause-{@code Throwable's}
146     * that are present in this {@code Throwable}.
147     *
148     * @param t Any Java {@code Throwable}.  Java {@code Throwable's} with one or {@code 'cause'}
149     * {@code Throwable's} will utilize more fully the printing-features of this class,
150     * {@code 'EXCC'} - <I>though any {@code Throwable} will be properly printed</I>.
151     * 
152     * @param maxNumInvocations Each of the {@code Throwable's} printed shall have, at most,
153     * {@code 'maxNumInvocations'} of their Stack-Traces printed into the output {@code String}.
154     * 
155     * @return This method doesn't actually print anything to the screen or terminal, it just
156     * returns a {@code String} that you may print yourself - <I>or write to a
157     * {@code class StringBuilder}, or any variation of text-output you wish.</I>
158     */
159    public static String toStringMaxTraces(Throwable t, int maxNumInvocations)
160    {
161        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
162        // Initialize the Variables
163        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
164
165        StackTraceElement[] steArr = t.getStackTrace();
166
167        final StringBuilder sb              = new StringBuilder();
168        final Throwable     cause           = t.getCause();
169        final Throwable     legacyCause     = legacyCause(t);
170        final boolean       hasCause        = (cause != null);
171        final boolean       hasLegacyCause  = (legacyCause != null) && (legacyCause != cause);
172
173        String msg          = t.getMessage();
174        String localMsg     = t.getLocalizedMessage();
175        String legacyMsg    = (hasLegacyCause) ? legacyMsg(t) : null;
176
177        final boolean hasMessage    = (msg != null)         && (msg.length() > 0);
178        final boolean hasLocalMsg   = (localMsg != null)    && (localMsg.length() > 0);
179
180        if (hasMessage)     msg         = StrIndent.indent(msg, 1, false, true);
181        if (hasLocalMsg)    localMsg    = StrIndent.indent(localMsg, 1, false, true);
182
183
184        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
185        // Print the "Top Part" / "Header Part" / Error-Message to the Return-Output StringBuilder
186        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
187
188        sb.append(BRED + "THROWN: " + t.getClass().getCanonicalName() + RESET + "\n");
189
190        if (hasMessage)
191        {
192            sb.append(BCYAN + "Throwable.getMessage():\n" + RESET + msg + "\n");
193
194            if (hasLocalMsg && (! msg.equals(localMsg)))
195                sb.append(BCYAN + "Throwable.getLocalizedMessage():\n" + RESET + localMsg + "\n");
196        }
197
198        else if (hasLocalMsg)
199            sb.append(BCYAN + "Throwable.getLocalizedMessage():\n" + RESET + localMsg + "\n");
200
201        else
202            sb.append(BYELLOW + "No Exception Messages Provided.\n" + RESET);
203
204
205        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
206        // Print the Stack-Trace
207        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
208
209        sb.append(BCYAN + "StackTrace:\n" + RESET);
210
211        int temp, maxLN = 0, stTruncated = 0;
212
213        if (steArr.length > maxNumInvocations)
214        {
215            stTruncated = steArr.length - maxNumInvocations;
216            steArr      = Arrays.copyOf(steArr, maxNumInvocations);
217        }
218
219        for (StackTraceElement ste : steArr)
220            if ((temp = ste.getLineNumber()) > maxLN)
221                maxLN = temp;
222
223        final int base10 =
224            2 + // 2: colon + space
225            ((int) Math.ceil(Math.log10(maxLN)));
226
227
228        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
229        // Build the StringBuilder & Return / Exit
230        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
231
232        for (int k=0; k < steArr.length; k++) sb.append(
233            '\t' + StringParse.rightSpacePad(steArr[k].getLineNumber() + ":", base10) +
234            steArr[k].getClassName() + "." +
235            steArr[k].getMethodName() + "()\n"
236        );
237
238        if (stTruncated > 0)
239            sb.append("\t... and " + stTruncated + " more invocations.\n");
240
241
242        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
243        // Build the StringBuilder & Return / Exit
244        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
245
246        return
247            sb.toString() +
248            (hasCause       ? StrIndent.indentTabs(toString(cause), 1)                      : "") +
249            (hasLegacyCause ? (legacyMsg + StrIndent.indentTabs(toString(legacyCause), 1))  : "");
250    }
251
252
253    // ********************************************************************************************
254    // ********************************************************************************************
255    // Variant: No Stack Trace Printing
256    // ********************************************************************************************
257    // ********************************************************************************************
258
259
260    /**
261     * Convenience Method.
262     * <BR />Invokes: {@link #toStringNoST(Throwable)}
263     * <BR />And-Then: {@link StrIndent#indentTabs(String, int)}
264     */
265    public static String toStringNoST(Throwable t, int indentation)
266    { return StrIndent.indentTabs(toStringNoST(t), indentation); }
267
268    /**
269     * Prints the {@code Exception}-message to an output {@code String}.  Invokes, recursively,
270     * this method with any cause-{@code Throwable's} that are present in this {@code Throwable}.
271     *
272     * <BR /><BR /><DIV CLASS=JDHint>
273     * This method differs from {@link #toString(Throwable)}, in that it <B>does not print the
274     * {@code StackTrace's} to the output-return {@code String}.</B>
275     * </DIV>
276     * 
277     * @param t Any Java {@code Throwable}.  Java {@code Throwable's} with one or {@code 'cause'}
278     * {@code Throwable's} will utilize more fully the printing-features of this class,
279     * {@code 'EXCC'} - <I>though any {@code Throwable} will be properly printed</I>.
280     * 
281     * @return This method doesn't actually print anything to the screen or terminal, it just
282     * returns a {@code String} that you may print yourself - <I>or write to a
283     * {@code class StorageBuffer,} or any variation of logging you wish.</I>
284     */
285    public static String toStringNoST(Throwable t)
286    {
287        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
288        // Initialize the Variables
289        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
290
291        final StringBuilder sb              = new StringBuilder();
292        final Throwable     cause           = t.getCause();
293        final Throwable     legacyCause     = legacyCause(t);
294        final boolean       hasCause        = (cause != null);
295        final boolean       hasLegacyCause  = (legacyCause != null) && (legacyCause != cause);
296
297        String msg          = t.getMessage();
298        String localMsg     = t.getLocalizedMessage();
299        String legacyMsg    = (hasLegacyCause) ? legacyMsg(t) : null;
300
301        final boolean hasMessage    = (msg != null)         && (msg.length() > 0);
302        final boolean hasLocalMsg   = (localMsg != null)    && (localMsg.length() > 0);
303
304        if (hasMessage)     msg         = StrIndent.indent(msg, 1, false, true);
305        if (hasLocalMsg)    localMsg    = StrIndent.indent(localMsg, 1, false, true);
306
307
308        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
309        // Print the "Top Part" / "Header Part" / Error-Message to the Return-Output StringBuilder
310        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
311
312        sb.append(BRED + "THROWN: " + t.getClass().getCanonicalName() + RESET + "\n");
313
314        if (hasMessage)
315        {
316            sb.append(BCYAN + "Throwable.getMessage():\n" + RESET + msg + "\n");
317
318            if (hasLocalMsg && (! msg.equals(localMsg)))
319                sb.append(BCYAN + "Throwable.getLocalizedMessage():\n" + RESET + localMsg + "\n");
320        }
321
322        else if (hasLocalMsg)
323            sb.append(BCYAN + "Throwable.getLocalizedMessage():\n" + RESET + localMsg + "\n");
324
325        else
326            sb.append(BYELLOW + "No Exception Messages Provided.\n" + RESET);
327
328
329        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
330        // Build the StringBuilder & Return / Exit
331        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
332
333        return
334            sb.toString() +
335            (hasCause
336                ? StrIndent.indentTabs(toStringNoST(cause), 1)
337                : "") +
338            (hasLegacyCause
339                ? (legacyMsg + StrIndent.indentTabs(toStringNoST(legacyCause), 1))
340                : "");
341    }
342
343
344    // ********************************************************************************************
345    // ********************************************************************************************
346    // Two Helpers
347    // ********************************************************************************************
348    // ********************************************************************************************
349
350
351    /**
352     * Helper Method for retrieving exceptions that function in a very similar fashion to a
353     * {@code 'cause'} exception.
354     * 
355     * @param t Any Java Throwable
356     * 
357     * @return For legacy exception classes, this will return the {@code 'cause'}, using the older
358     * method for extracting that cause.
359     */
360    protected static Throwable legacyCause(final Throwable t)
361    {
362        if (t instanceof InvocationTargetException)
363            return ((InvocationTargetException) t).getTargetException();
364
365        if (t instanceof ExceptionInInitializerError)
366            return ((ExceptionInInitializerError) t).getException();
367
368        return null;
369    }
370
371    /**
372     * Generates a simple descriptive {@code String} whenever there is a cause exception present
373     * that has been extracted from one of the older exception classes that can produce such legacy
374     * causes.
375     * 
376     * @param t
377     * @return A descriptive {@code String} explaining the legacy cause feature
378     */
379    protected static String legacyMsg(final Throwable t)
380    {
381        if (t instanceof InvocationTargetException)
382            return "InvocationTargetException.getTargetException():\n";
383
384        if (t instanceof ExceptionInInitializerError)
385            return "ExceptionInInitializerError.getException():\n";
386
387        return null;
388    }
389}