View Javadoc
1   /*
2    * Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved.
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 2.1 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this program; if not, write to the Free Software
16   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17   *
18   * Additionally licensed with:
19   *
20   * Licensed under the Apache License, Version 2.0 (the "License");
21   * you may not use this file except in compliance with the License.
22   * You may obtain a copy of the License at
23   *
24   *      http://www.apache.org/licenses/LICENSE-2.0
25   *
26   * Unless required by applicable law or agreed to in writing, software
27   * distributed under the License is distributed on an "AS IS" BASIS,
28   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29   * See the License for the specific language governing permissions and
30   * limitations under the License.
31   */
32  package org.spf4j.base;
33  
34  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
35  import gnu.trove.set.hash.THashSet;
36  import java.io.IOException;
37  import java.io.PrintStream;
38  import java.io.UncheckedIOException;
39  import java.sql.SQLRecoverableException;
40  import java.sql.SQLTransientException;
41  import java.util.ArrayDeque;
42  import java.util.ArrayList;
43  import java.util.Iterator;
44  import java.util.List;
45  import java.util.Set;
46  import java.util.concurrent.ExecutionException;
47  import java.util.concurrent.TimeoutException;
48  import java.util.function.Function;
49  import java.util.function.Predicate;
50  import java.util.logging.Level;
51  import java.util.logging.Logger;
52  import javax.annotation.CheckReturnValue;
53  import javax.annotation.Nonnull;
54  import javax.annotation.Nullable;
55  import javax.annotation.ParametersAreNonnullByDefault;
56  import org.spf4j.base.avro.AThrowables;
57  import org.spf4j.base.avro.RemoteException;
58  import org.spf4j.ds.IdentityHashSet;
59  
60  /**
61   * utility class for throwables.
62   *
63   * @author zoly
64   */
65  @ParametersAreNonnullByDefault
66  @SuppressFBWarnings("FCCD_FIND_CLASS_CIRCULAR_DEPENDENCY")
67  // Circular dependency with AThrowables, both static utility classes... should be fine...
68  public final class Throwables {
69  
70    /**
71     * Caption for labeling suppressed exception stack traces
72     */
73    public static final String SUPPRESSED_CAPTION = "Suppressed: ";
74    /**
75     * Caption for labeling causative exception stack traces
76     */
77    public static final String CAUSE_CAPTION = "Caused by: ";
78  
79  
80    private static final int MAX_SUPPRESS_CHAIN
81            = Integer.getInteger("spf4j.throwables.defaultMaxSuppressChain", 100);
82  
83    private static final PackageDetail DEFAULT_PACKAGE_DETAIL
84            = PackageDetail.valueOf(System.getProperty("spf4j.throwables.defaultStackTracePackageDetail", "SHORT"));
85  
86  
87    private static final boolean DEFAULT_TRACE_ELEMENT_ABBREVIATION
88            = Boolean.parseBoolean(System.getProperty("spf4j.throwables.defaultStackTraceAbbreviation", "true"));
89  
90  
91    private static volatile Predicate<Throwable> nonRecoverableClassificationPredicate = new Predicate<Throwable>() {
92      @Override
93      @SuppressFBWarnings("ITC_INHERITANCE_TYPE_CHECKING")
94      public boolean test(final Throwable t) {
95  
96        if (t instanceof Error && !(t instanceof StackOverflowError)
97                && !(t instanceof AssertionError)
98                && !(t.getClass().getName().endsWith("TokenMgrError"))) {
99          return true;
100       }
101       if (t instanceof IOException) {
102         String message = t.getMessage();
103         if (message != null && message.contains("Too many open files")) {
104           return true;
105         }
106       }
107       return false;
108     }
109   };
110 
111 
112   private static volatile Function<Throwable, Boolean> isRetryablePredicate =
113           new Function<Throwable, Boolean>() {
114     /**
115      * A default predicate that will answer if a exception is retry-able or not.
116      * @param t
117      * @return true if a exception is retry-able, false if it is not, null if this is not known.
118      */
119     @Override
120     @SuppressFBWarnings({"ITC_INHERITANCE_TYPE_CHECKING", "NP_BOOLEAN_RETURN_NULL" })
121     @Nullable
122     public Boolean apply(final Throwable t) {
123       // non recoverables are not retryable.
124       if (Throwables.containsNonRecoverable(t)) {
125         return Boolean.FALSE;
126       }
127       // check causal chaing
128       Throwable e = Throwables.firstCause(t,
129               ex -> {
130                 String exClassName = ex.getClass().getName();
131                 return (ex instanceof SQLTransientException
132               || ex instanceof SQLRecoverableException
133               || (ex instanceof IOException && !exClassName.contains("Json"))
134               || ex instanceof TimeoutException
135               || ex instanceof UncheckedTimeoutException
136               || (exClassName.contains("Transient")
137                         && !exClassName.contains("NonTransient")));
138                         });
139       return e != null ? Boolean.TRUE : null;
140     }
141   };
142 
143 
144   private Throwables() {
145   }
146 
147   /**
148    * figure out if a Exception is retry-able or not.
149    * If while executing a operation a exception is returned, that exception is retryable if retrying the operation
150    * can potentially succeed.
151    * @param value
152    * @return true/false is retry-able or not, null when this is not clear and can be context dependent.
153    */
154   @CheckReturnValue
155   public static boolean isRetryable(final Throwable value) {
156     Boolean result = isRetryablePredicate.apply(value);
157     return result == null ? false : result;
158   }
159 
160   @Nonnull
161   @CheckReturnValue
162   public static Function<Throwable, Boolean> getIsRetryablePredicate() {
163     return isRetryablePredicate;
164   }
165 
166   public static void setIsRetryablePredicate(final Function<Throwable, Boolean> isRetryablePredicate) {
167     Throwables.isRetryablePredicate = isRetryablePredicate;
168   }
169 
170 
171   public static int getNrSuppressedExceptions(final Throwable t) {
172     return UnsafeThrowable.getSuppressedNoCopy(t).size();
173   }
174 
175   public static int getNrRecursiveSuppressedExceptions(final Throwable t) {
176     final List<Throwable> suppressedExceptions = UnsafeThrowable.getSuppressedNoCopy(t);
177     int count = 0;
178     for (Throwable se : suppressedExceptions) {
179       count += 1 + getNrRecursiveSuppressedExceptions(se);
180     }
181     return count;
182   }
183 
184   @Nullable
185   public static Throwable removeOldestSuppressedRecursive(final Throwable t) {
186       final List<Throwable> suppressedExceptions = UnsafeThrowable.getSuppressedNoCopy(t);
187       if (suppressedExceptions.isEmpty()) {
188         return null;
189       } else {
190         Throwable ex = suppressedExceptions.get(0);
191         if (getNrSuppressedExceptions(ex) > 0) {
192           return removeOldestSuppressedRecursive(ex);
193         } else {
194           return suppressedExceptions.remove(0);
195         }
196       }
197   }
198 
199   @Nullable
200   public static Throwable removeOldestSuppressed(final Throwable t) {
201     final List<Throwable> suppressedExceptions = UnsafeThrowable.getSuppressedNoCopy(t);
202     if (suppressedExceptions.isEmpty()) {
203       return null;
204     } else {
205       return suppressedExceptions.remove(0);
206     }
207   }
208 
209 
210   public static final class TrimmedException extends Exception {
211 
212     public TrimmedException(final String message) {
213       super(message);
214     }
215 
216     @Override
217     public synchronized Throwable fillInStackTrace() {
218       return this;
219     }
220   }
221 
222 
223   /**
224    * Functionality will call Throwable.addSuppressed, 2 extra things happen:
225    *
226    * 1) limit to nr of exceptions suppressed.
227    * 2) if exception is already suppressed, will not add it.
228    * 3) will return a clone of exception t.
229    *
230    * @param <T>
231    * @param t
232    * @param suppressed
233    * @returna clone of exception t with suppressed exception suppressed;
234    * @deprecated use suppressLimited instead.
235    *
236    */
237   @CheckReturnValue
238   @Deprecated
239   public static <T extends Throwable> T suppress(@Nonnull final T t, @Nonnull final Throwable suppressed) {
240     return suppress(t, suppressed, MAX_SUPPRESS_CHAIN);
241   }
242 
243   /**
244    * Functionality will call Throwable.addSuppressed, 2 extra things happen:
245    *
246    * 1) limit to nr of exceptions suppressed.
247    * 2) if exception is already suppressed, will not add it.
248    *
249    * @param t
250    * @param suppressed
251    */
252   public static void suppressLimited(@Nonnull final Throwable t, @Nonnull final Throwable suppressed) {
253     suppressLimited(t, suppressed, MAX_SUPPRESS_CHAIN);
254   }
255 
256   @SuppressFBWarnings("NOS_NON_OWNED_SYNCHRONIZATION")
257   public static void suppressLimited(@Nonnull final Throwable t, @Nonnull final Throwable suppressed,
258           final int maxSuppressed) {
259     if (contains(t, suppressed)) { //protect against circular references.
260       Logger.getLogger(Throwables.class.getName()).log(Level.INFO,
261               "Circular suppression attempted", new RuntimeException(suppressed));
262       return;
263     }
264     synchronized (t) {
265       t.addSuppressed(suppressed);
266       while (getNrRecursiveSuppressedExceptions(t) > maxSuppressed) {
267         if (removeOldestSuppressedRecursive(t) == null) {
268           throw new IllegalArgumentException("Impossible state for " + t);
269         }
270       }
271     }
272   }
273 
274 
275   @CheckReturnValue
276   public static <T extends Throwable> T suppress(@Nonnull final T t, @Nonnull final Throwable suppressed,
277           final int maxSuppressed) {
278     T clone;
279     try {
280       clone = Objects.clone(t);
281     } catch (RuntimeException ex) {
282       t.addSuppressed(ex);
283       clone = t;
284     }
285     if (contains(t, suppressed)) {
286       return clone;
287     }
288     clone.addSuppressed(suppressed);
289     while (getNrRecursiveSuppressedExceptions(clone) > maxSuppressed) {
290       if (removeOldestSuppressedRecursive(clone) == null) {
291         throw new IllegalArgumentException("Impossible state for " + clone);
292       }
293     }
294     return clone;
295   }
296 
297   /**
298    * Utility to get suppressed exceptions.
299    *
300    * In java 1.7 it will return t.getSuppressed()
301    * + in case it is Iterable<Throwable> any other linked exceptions (see
302    * SQLException)
303    *
304    * java 1.6 behavior is deprecated.
305    *
306    * @param t
307    * @return
308    */
309   public static Throwable[] getSuppressed(final Throwable t) {
310     if (t instanceof Iterable) {
311       // see SQLException
312       Throwable[] osuppressed = t.getSuppressed();
313       List<Throwable> suppressed = new ArrayList<>(osuppressed.length);
314       Set<Throwable> ignore = new IdentityHashSet<>();
315       ignore.addAll(java.util.Arrays.asList(osuppressed));
316       suppressed.addAll(ignore);
317       ignore.addAll(com.google.common.base.Throwables.getCausalChain(t));
318       Iterator it = ((Iterable) t).iterator();
319       while (it.hasNext()) {
320         Object next = it.next();
321         if (next instanceof Throwable) {
322           if (ignore.contains(next)) {
323             continue;
324           }
325           suppressed.add((Throwable) next);
326           ignore.addAll(com.google.common.base.Throwables.getCausalChain((Throwable) next));
327         } else {
328           break;
329         }
330       }
331       return suppressed.toArray(new Throwable[suppressed.size()]);
332     } else {
333       return t.getSuppressed();
334     }
335 
336   }
337 
338   public static void writeTo(final StackTraceElement element, @Nullable final StackTraceElement previous,
339           final Appendable to, final PackageDetail detail,
340           final boolean abbreviatedTraceElement)
341           throws IOException {
342     String currClassName = element.getClassName();
343     String prevClassName = previous == null ? null : previous.getClassName();
344     if (abbreviatedTraceElement) {
345       if (currClassName.equals(prevClassName)) {
346         to.append('^');
347       } else {
348         writeAbreviatedClassName(currClassName, to);
349       }
350     } else {
351       to.append(currClassName);
352     }
353     to.append('.');
354     to.append(element.getMethodName());
355     String currFileName = element.getFileName();
356     String fileName = currFileName;
357     if (abbreviatedTraceElement && java.util.Objects.equals(currFileName,
358             previous == null ? null : previous.getFileName())) {
359       fileName = "^";
360     }
361     final int lineNumber = element.getLineNumber();
362     if (element.isNativeMethod()) {
363       to.append("(Native Method)");
364     } else if (fileName == null) {
365       to.append("(Unknown Source)");
366     } else if (lineNumber >= 0) {
367       to.append('(').append(fileName).append(':')
368               .append(Integer.toString(lineNumber)).append(')');
369     } else {
370       to.append('(').append(fileName).append(')');
371     }
372     if (detail == PackageDetail.NONE) {
373       return;
374     }
375     if (abbreviatedTraceElement && currClassName.equals(prevClassName)) {
376       to.append("[^]");
377       return;
378     }
379     org.spf4j.base.avro.PackageInfo pInfo = PackageInfo.getPackageInfo(currClassName);
380     if (abbreviatedTraceElement && prevClassName != null && pInfo.equals(PackageInfo.getPackageInfo(prevClassName))) {
381       to.append("[^]");
382       return;
383     }
384     if (!pInfo.getUrl().isEmpty() || !pInfo.getVersion().isEmpty()) {
385       String jarSourceUrl = pInfo.getUrl();
386       String version = pInfo.getVersion();
387       to.append('[');
388       if (!jarSourceUrl.isEmpty()) {
389         if (detail == PackageDetail.SHORT) {
390           String url = jarSourceUrl;
391           int lastIndexOf = url.lastIndexOf('/');
392           if (lastIndexOf >= 0) {
393             int lpos = url.length() - 1;
394             if (lastIndexOf == lpos) {
395               int prevSlPos = url.lastIndexOf('/', lpos - 1);
396               if (prevSlPos < 0) {
397                 to.append(url);
398               } else {
399                 to.append(url, prevSlPos + 1, url.length());
400               }
401             } else {
402               to.append(url, lastIndexOf + 1, url.length());
403             }
404           } else {
405             to.append(url);
406           }
407         } else {
408           to.append(jarSourceUrl);
409         }
410       } else {
411         to.append("na");
412       }
413       if (!version.isEmpty()) {
414         to.append(':');
415         to.append(version);
416       }
417       to.append(']');
418     }
419   }
420 
421   /**
422    * enum describing the PackageDetail level to be logged in the stack trace.
423    */
424   public enum PackageDetail {
425     /**
426      * No jar info or version info.
427      */
428     NONE,
429     /**
430      * jar file name + manifest version.
431      */
432     SHORT,
433     /**
434      * complete jar path + manifest version.
435      */
436     LONG
437 
438   }
439 
440   public static String toString(final Throwable t) {
441     return toString(t, DEFAULT_PACKAGE_DETAIL);
442   }
443 
444   public static String toString(final Throwable t, final PackageDetail detail) {
445     return toString(t, detail, DEFAULT_TRACE_ELEMENT_ABBREVIATION);
446   }
447 
448   public static String toString(final Throwable t, final PackageDetail detail, final boolean abbreviatedTraceElement) {
449     StringBuilder sb = toStringBuilder(t, detail, abbreviatedTraceElement);
450     return sb.toString();
451   }
452 
453   public static StringBuilder toStringBuilder(final Throwable t, final PackageDetail detail) {
454     return toStringBuilder(t, detail, DEFAULT_TRACE_ELEMENT_ABBREVIATION);
455   }
456 
457   public static StringBuilder toStringBuilder(final Throwable t, final PackageDetail detail,
458           final boolean abbreviatedTraceElement) {
459     StringBuilder sb = new StringBuilder(1024);
460     try {
461       writeTo(t, sb, detail, abbreviatedTraceElement);
462     } catch (IOException ex) {
463       throw new RuntimeException(ex);
464     }
465     return sb;
466   }
467 
468   public static void writeTo(@Nonnull final Throwable t, @Nonnull final PrintStream to,
469           @Nonnull final PackageDetail detail) {
470     writeTo(t, to, detail, DEFAULT_TRACE_ELEMENT_ABBREVIATION);
471   }
472 
473   @SuppressFBWarnings({"OCP_OVERLY_CONCRETE_PARAMETER"}) // on purpose :-)
474   public static void writeTo(@Nonnull final Throwable t, @Nonnull final PrintStream to,
475           @Nonnull final PackageDetail detail, final boolean abbreviatedTraceElement) {
476     StringBuilder sb = new StringBuilder(1024);
477     try {
478       writeTo(t, sb, detail, abbreviatedTraceElement);
479       to.append(sb);
480     } catch (IOException ex) {
481       throw new UncheckedIOException(ex);
482     }
483   }
484 
485   public static void writeTo(final Throwable t, final Appendable to, final PackageDetail detail) throws IOException {
486     writeTo(t, to, detail, DEFAULT_TRACE_ELEMENT_ABBREVIATION);
487   }
488 
489   public static void writeTo(final Throwable t, final Appendable to, final PackageDetail detail,
490           final String prefix) throws IOException {
491     writeTo(t, to, detail, DEFAULT_TRACE_ELEMENT_ABBREVIATION, prefix);
492   }
493 
494   public static void writeTo(final Throwable t, final Appendable to, final PackageDetail detail,
495           final boolean abbreviatedTraceElement) throws IOException {
496     writeTo(t, to, detail, abbreviatedTraceElement, "");
497   }
498 
499   public static void writeTo(final Throwable t, final Appendable to, final PackageDetail detail,
500           final boolean abbreviatedTraceElement, final String prefix) throws IOException {
501     if (t instanceof RemoteException) {
502       AThrowables.writeTo((RemoteException) t, to, detail, abbreviatedTraceElement, prefix);
503       return;
504     }
505     to.append(prefix);
506     writeMessageString(to, t);
507     to.append('\n');
508     writeThrowableDetail(t, to, detail, abbreviatedTraceElement, prefix);
509   }
510 
511   public static void writeThrowableDetail(final Throwable t, final Appendable to, final PackageDetail detail,
512           final boolean abbreviatedTraceElement, final String prefix) throws IOException {
513     StackTraceElement[] trace = t.getStackTrace();
514     writeTo(trace, to, detail, abbreviatedTraceElement, prefix);
515     Throwable[] suppressed = getSuppressed(t);
516     Throwable ourCause = t.getCause();
517     if (ourCause == null && suppressed.length == 0) {
518       return;
519     }
520     Set<Throwable> dejaVu = new IdentityHashSet<Throwable>();
521     dejaVu.add(t);
522     // Print suppressed exceptions, if any
523     for (Throwable se : suppressed) {
524       printEnclosedStackTrace(se, to, trace, SUPPRESSED_CAPTION, prefix + "\t",
525               dejaVu, detail, abbreviatedTraceElement);
526     }
527     // Print cause, if any
528     if (ourCause != null) {
529       printEnclosedStackTrace(ourCause, to, trace, CAUSE_CAPTION, prefix, dejaVu, detail, abbreviatedTraceElement);
530     }
531   }
532 
533   public static void writeMessageString(final Appendable to, final Throwable t) throws IOException {
534     to.append(t.getClass().getName());
535     String message = t.getMessage();
536     if (message != null) {
537       to.append(':').append(message);
538     }
539   }
540 
541   public static void writeTo(final StackTraceElement[] trace, final Appendable to, final PackageDetail detail,
542           final boolean abbreviatedTraceElement)
543           throws IOException {
544     writeTo(trace, to, detail, abbreviatedTraceElement, "");
545   }
546 
547   public static void writeTo(final StackTraceElement[] trace, final Appendable to, final PackageDetail detail,
548           final boolean abbreviatedTraceElement, final String prefix)
549           throws IOException {
550     StackTraceElement prevElem = null;
551     for (StackTraceElement traceElement : trace) {
552       to.append(prefix);
553       to.append("\tat ");
554       writeTo(traceElement, prevElem, to, detail, abbreviatedTraceElement);
555       to.append('\n');
556       prevElem = traceElement;
557     }
558   }
559 
560   public static int commonFrames(final StackTraceElement[] trace, final StackTraceElement[] enclosingTrace) {
561     int from = trace.length - 1;
562     int m = from;
563     int n = enclosingTrace.length - 1;
564     while (m >= 0 && n >= 0 && trace[m].equals(enclosingTrace[n])) {
565       m--;
566       n--;
567     }
568     return from - m;
569   }
570 
571   private static void printEnclosedStackTrace(final Throwable t, final Appendable s,
572           final StackTraceElement[] enclosingTrace,
573           final String caption,
574           final String prefix,
575           final Set<Throwable> dejaVu,
576           final PackageDetail detail,
577           final boolean abbreviatedTraceElement) throws IOException {
578     if (dejaVu.contains(t)) {
579       s.append("\t[CIRCULAR REFERENCE:");
580       writeMessageString(s, t);
581       s.append("]\n");
582     } else {
583       dejaVu.add(t);
584       // Compute number of frames in common between this and enclosing trace
585       StackTraceElement[] trace = t.getStackTrace();
586       int framesInCommon = commonFrames(trace, enclosingTrace);
587       int m = trace.length - framesInCommon;
588       // Print our stack trace
589       s.append(prefix).append(caption);
590       writeMessageString(s, t);
591       s.append('\n');
592       StackTraceElement prev = null;
593       for (int i = 0; i < m; i++) {
594         s.append(prefix).append("\tat ");
595         StackTraceElement ste = trace[i];
596         writeTo(ste, prev, s, detail, abbreviatedTraceElement);
597         s.append('\n');
598         prev = ste;
599       }
600       if (framesInCommon != 0) {
601         s.append(prefix).append("\t... ").append(Integer.toString(framesInCommon)).append(" more");
602         s.append('\n');
603       }
604 
605       // Print suppressed exceptions, if any
606       for (Throwable se : getSuppressed(t)) {
607         printEnclosedStackTrace(se, s, trace, SUPPRESSED_CAPTION, prefix + '\t',
608                 dejaVu, detail, abbreviatedTraceElement);
609       }
610 
611       // Print cause, if any
612       Throwable ourCause = t.getCause();
613       if (ourCause != null) {
614         printEnclosedStackTrace(ourCause, s, trace, CAUSE_CAPTION, prefix,
615                 dejaVu, detail, abbreviatedTraceElement);
616       }
617     }
618   }
619 
620   /**
621    * Is this Throwable a JVM non-recoverable exception. (Oom, VMError, etc...)
622    * @param t
623    * @return
624    */
625   public static boolean isNonRecoverable(@Nonnull final Throwable t) {
626     return nonRecoverableClassificationPredicate.test(t);
627   }
628 
629   /**
630    * Does this Throwable contain a JVM non-recoverable exception. (Oom, VMError, etc...)
631    * @param t
632    * @return
633    */
634   public static boolean containsNonRecoverable(@Nonnull final Throwable t) {
635     return contains(t, nonRecoverableClassificationPredicate);
636   }
637 
638   /**
639    * checks in the throwable + children (both causal and suppressed) contain a throwable that
640    * respects the Predicate.
641    * @param t the throwable
642    * @param predicate the predicate
643    * @return true if a Throwable matching the predicate is found.
644    */
645   public static boolean contains(@Nonnull final Throwable t, final Predicate<Throwable> predicate) {
646     return first(t, predicate) != null;
647   }
648 
649 
650   /**
651    * checks in the throwable + children (both causal and suppressed) contain a throwable that
652    * respects the Predicate.
653    * @param t the throwable
654    * @param predicate the predicate
655    * @return true if a Throwable matching the predicate is found.
656    */
657   public static boolean contains(@Nonnull final Throwable t, @Nonnull final Throwable toLookFor) {
658     return first(t, (x) -> x == toLookFor) != null;
659   }
660 
661   /**
662    * return first Exception in the causal chain Assignable to clasz.
663    * @param <T>
664    * @param t
665    * @param clasz
666    * @return
667    */
668   @Nullable
669   @CheckReturnValue
670   public static <T extends Throwable> T first(@Nonnull final Throwable t, final Class<T> clasz) {
671     return (T) first(t, (Throwable th) -> clasz.isAssignableFrom(th.getClass()));
672   }
673 
674   /**
675    * Returns the first Throwable that matches the predicate in the causal and suppressed chain,
676    * the suppressed chain includes the supression mechanism included in SQLException.
677    * @param t the Throwable
678    * @param predicate the Predicate
679    * @return the Throwable the first matches the predicate or null is none matches.
680    */
681   @Nullable
682   @CheckReturnValue
683   public static Throwable first(@Nonnull final Throwable t, final Predicate<Throwable> predicate) {
684     if (predicate.test(t)) { //shortcut
685       return t;
686     }
687     ArrayDeque<Throwable> toScan =  new ArrayDeque<>();
688     Throwable cause = t.getCause();
689     if (cause != null) {
690       toScan.addFirst(cause);
691     }
692     for (Throwable supp : getSuppressed(t)) {
693       toScan.addLast(supp);
694     }
695     Throwable th;
696     THashSet<Throwable> seen = new IdentityHashSet<>();
697     while ((th = toScan.pollFirst()) != null) {
698       if (seen.contains(th)) {
699         continue;
700       }
701       if (predicate.test(th)) {
702         return th;
703       } else {
704         cause = th.getCause();
705         if (cause != null) {
706           toScan.addFirst(cause);
707         }
708         for (Throwable supp : getSuppressed(th)) {
709           toScan.addLast(supp);
710         }
711       }
712       seen.add(th);
713     }
714     return null;
715   }
716 
717 
718   /**
719    * Returns first Throwable in the causality chain that is matching the provided predicate.
720    * @param throwable the Throwable to go through.
721    * @param predicate the predicate to apply
722    * @return the first Throwable from the chain that the predicate matches.
723    */
724 
725   @Nullable
726   @CheckReturnValue
727   public static Throwable firstCause(@Nonnull final Throwable throwable, final Predicate<Throwable> predicate) {
728     Throwable t = throwable;
729     do {
730       if (predicate.test(t)) {
731         return t;
732       }
733       t = t.getCause();
734     } while (t != null);
735     return null;
736   }
737 
738 
739   public static Predicate<Throwable> getNonRecoverablePredicate() {
740     return nonRecoverableClassificationPredicate;
741   }
742 
743   /**
744    * Overwrite the default non-recoverable predicate.
745    * @param predicate
746    */
747   public static void setNonRecoverablePredicate(final Predicate<Throwable> predicate) {
748     Throwables.nonRecoverableClassificationPredicate = predicate;
749   }
750 
751   public static void writeAbreviatedClassName(final String className, final Appendable writeTo) throws IOException {
752     int ldIdx = className.lastIndexOf('.');
753     if (ldIdx < 0) {
754       writeTo.append(className);
755       return;
756     }
757     boolean isPreviousDot = true;
758     for (int i = 0; i < ldIdx; i++) {
759       char c = className.charAt(i);
760       boolean isCurrentCharDot = c == '.';
761       if (isPreviousDot || isCurrentCharDot) {
762         writeTo.append(c);
763       }
764       isPreviousDot = isCurrentCharDot;
765     }
766     writeTo.append(className, ldIdx, className.length());
767   }
768 
769   @SuppressFBWarnings("ITC_INHERITANCE_TYPE_CHECKING")
770   public static void throwException(final Exception ex) throws IOException, InterruptedException,
771           ExecutionException, TimeoutException {
772     if (ex instanceof IOException) {
773       throw (IOException) ex;
774     } else if (ex instanceof InterruptedException) {
775       throw (InterruptedException) ex;
776     } else if (ex instanceof ExecutionException) {
777       throw (ExecutionException) ex;
778     } else if (ex instanceof TimeoutException) {
779       throw (TimeoutException) ex;
780     } else {
781       throw new ExecutionException(ex);
782     }
783   }
784 
785 
786 }