View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.impl;
18  
19  import java.io.Serializable;
20  import java.net.URL;
21  import java.security.CodeSource;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.Stack;
30  
31  import org.apache.logging.log4j.core.pattern.PlainTextRenderer;
32  import org.apache.logging.log4j.core.pattern.TextRenderer;
33  import org.apache.logging.log4j.core.util.Loader;
34  import org.apache.logging.log4j.status.StatusLogger;
35  import org.apache.logging.log4j.util.LoaderUtil;
36  import org.apache.logging.log4j.util.StackLocatorUtil;
37  import org.apache.logging.log4j.util.Strings;
38  
39  /**
40   * Wraps a Throwable to add packaging information about each stack trace element.
41   *
42   * <p>
43   * A proxy is used to represent a throwable that may not exist in a different class loader or JVM. When an application
44   * deserializes a ThrowableProxy, the throwable may not be set, but the throwable's information is preserved in other
45   * fields of the proxy like the message and stack trace.
46   * </p>
47   *
48   * <p>
49   * TODO: Move this class to org.apache.logging.log4j.core because it is used from LogEvent.
50   * </p>
51   * <p>
52   * TODO: Deserialize: Try to rebuild Throwable if the target exception is in this class loader?
53   * </p>
54   */
55  public class ThrowableProxy implements Serializable {
56  
57      private static final String TAB = "\t";
58      private static final String CAUSED_BY_LABEL = "Caused by: ";
59      private static final String SUPPRESSED_LABEL = "Suppressed: ";
60      private static final String WRAPPED_BY_LABEL = "Wrapped by: ";
61  
62      /**
63       * Cached StackTracePackageElement and ClassLoader.
64       * <p>
65       * Consider this class private.
66       * </p>
67       */
68      static class CacheEntry {
69          private final ExtendedClassInfo element;
70          private final ClassLoader loader;
71  
72          public CacheEntry(final ExtendedClassInfo element, final ClassLoader loader) {
73              this.element = element;
74              this.loader = loader;
75          }
76      }
77  
78      private static final ThrowableProxy[] EMPTY_THROWABLE_PROXY_ARRAY = new ThrowableProxy[0];
79  
80      private static final char EOL = '\n';
81  
82      private static final String EOL_STR = String.valueOf(EOL);
83  
84      private static final long serialVersionUID = -2752771578252251910L;
85  
86      private final ThrowableProxy causeProxy;
87  
88      private int commonElementCount;
89  
90      private final ExtendedStackTraceElement[] extendedStackTrace;
91  
92      private final String localizedMessage;
93  
94      private final String message;
95  
96      private final String name;
97  
98      private final ThrowableProxy[] suppressedProxies;
99  
100     private final transient Throwable throwable;
101 
102     /**
103      * For JSON and XML IO via Jackson.
104      */
105     @SuppressWarnings("unused")
106     private ThrowableProxy() {
107         this.throwable = null;
108         this.name = null;
109         this.extendedStackTrace = null;
110         this.causeProxy = null;
111         this.message = null;
112         this.localizedMessage = null;
113         this.suppressedProxies = EMPTY_THROWABLE_PROXY_ARRAY;
114     }
115 
116     /**
117      * Constructs the wrapper for the Throwable that includes packaging data.
118      *
119      * @param throwable The Throwable to wrap, must not be null.
120      */
121     public ThrowableProxy(final Throwable throwable) {
122         this(throwable, null);
123     }
124 
125     /**
126      * Constructs the wrapper for the Throwable that includes packaging data.
127      *
128      * @param throwable The Throwable to wrap, must not be null.
129      * @param visited   The set of visited suppressed exceptions.
130      */
131     private ThrowableProxy(final Throwable throwable, final Set<Throwable> visited) {
132         this.throwable = throwable;
133         this.name = throwable.getClass().getName();
134         this.message = throwable.getMessage();
135         this.localizedMessage = throwable.getLocalizedMessage();
136         final Map<String, CacheEntry> map = new HashMap<>();
137         final Stack<Class<?>> stack = StackLocatorUtil.getCurrentStackTrace();
138         this.extendedStackTrace = this.toExtendedStackTrace(stack, map, null, throwable.getStackTrace());
139         final Throwable throwableCause = throwable.getCause();
140         final Set<Throwable> causeVisited = new HashSet<>(1);
141         this.causeProxy = throwableCause == null ? null : new ThrowableProxy(throwable, stack, map, throwableCause,
142             visited, causeVisited);
143         this.suppressedProxies = this.toSuppressedProxies(throwable, visited);
144     }
145 
146     /**
147      * Constructs the wrapper for a Throwable that is referenced as the cause by another Throwable.
148      *
149      * @param parent            The Throwable referencing this Throwable.
150      * @param stack             The Class stack.
151      * @param map               The cache containing the packaging data.
152      * @param cause             The Throwable to wrap.
153      * @param suppressedVisited TODO
154      * @param causeVisited      TODO
155      */
156     private ThrowableProxy(final Throwable parent, final Stack<Class<?>> stack, final Map<String, CacheEntry> map,
157                            final Throwable cause, final Set<Throwable> suppressedVisited,
158                            final Set<Throwable> causeVisited) {
159         causeVisited.add(cause);
160         this.throwable = cause;
161         this.name = cause.getClass().getName();
162         this.message = this.throwable.getMessage();
163         this.localizedMessage = this.throwable.getLocalizedMessage();
164         this.extendedStackTrace = this.toExtendedStackTrace(stack, map, parent.getStackTrace(), cause.getStackTrace());
165         final Throwable causeCause = cause.getCause();
166         this.causeProxy = causeCause == null || causeVisited.contains(causeCause) ? null : new ThrowableProxy(parent,
167             stack, map, causeCause, suppressedVisited, causeVisited);
168         this.suppressedProxies = this.toSuppressedProxies(cause, suppressedVisited);
169     }
170 
171     @Override
172     public boolean equals(final Object obj) {
173         if (this == obj) {
174             return true;
175         }
176         if (obj == null) {
177             return false;
178         }
179         if (this.getClass() != obj.getClass()) {
180             return false;
181         }
182         final ThrowableProxy other = (ThrowableProxy) obj;
183         if (this.causeProxy == null) {
184             if (other.causeProxy != null) {
185                 return false;
186             }
187         } else if (!this.causeProxy.equals(other.causeProxy)) {
188             return false;
189         }
190         if (this.commonElementCount != other.commonElementCount) {
191             return false;
192         }
193         if (this.name == null) {
194             if (other.name != null) {
195                 return false;
196             }
197         } else if (!this.name.equals(other.name)) {
198             return false;
199         }
200         if (!Arrays.equals(this.extendedStackTrace, other.extendedStackTrace)) {
201             return false;
202         }
203         if (!Arrays.equals(this.suppressedProxies, other.suppressedProxies)) {
204             return false;
205         }
206         return true;
207     }
208 
209     private void formatCause(final StringBuilder sb, final String prefix, final ThrowableProxy cause,
210                              final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
211         formatThrowableProxy(sb, prefix, CAUSED_BY_LABEL, cause, ignorePackages, textRenderer, suffix, lineSeparator);
212     }
213 
214     private void formatThrowableProxy(final StringBuilder sb, final String prefix, final String causeLabel,
215                                       final ThrowableProxy throwableProxy, final List<String> ignorePackages,
216                                       final TextRenderer textRenderer, final String suffix, String lineSeparator) {
217         if (throwableProxy == null) {
218             return;
219         }
220         textRenderer.render(prefix, sb, "Prefix");
221         textRenderer.render(causeLabel, sb, "CauseLabel");
222         throwableProxy.renderOn(sb, textRenderer);
223         renderSuffix(suffix, sb, textRenderer);
224         textRenderer.render(lineSeparator, sb, "Text");
225         this.formatElements(sb, prefix, throwableProxy.commonElementCount,
226             throwableProxy.getStackTrace(), throwableProxy.extendedStackTrace, ignorePackages, textRenderer, suffix, lineSeparator);
227         this.formatSuppressed(sb, prefix + TAB, throwableProxy.suppressedProxies, ignorePackages, textRenderer, suffix, lineSeparator);
228         this.formatCause(sb, prefix, throwableProxy.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator);
229     }
230 
231     void renderOn(final StringBuilder output, final TextRenderer textRenderer) {
232         final String msg = this.message;
233         textRenderer.render(this.name, output, "Name");
234         if (msg != null) {
235             textRenderer.render(": ", output, "NameMessageSeparator");
236             textRenderer.render(msg, output, "Message");
237         }
238     }
239 
240     private void formatSuppressed(final StringBuilder sb, final String prefix, final ThrowableProxy[] suppressedProxies,
241                                   final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
242         if (suppressedProxies == null) {
243             return;
244         }
245         for (final ThrowableProxy suppressedProxy : suppressedProxies) {
246             formatThrowableProxy(sb, prefix, SUPPRESSED_LABEL, suppressedProxy, ignorePackages, textRenderer, suffix, lineSeparator);
247         }
248     }
249 
250     private void formatElements(final StringBuilder sb, final String prefix, final int commonCount,
251                                 final StackTraceElement[] causedTrace, final ExtendedStackTraceElement[] extStackTrace,
252                                 final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
253         if (ignorePackages == null || ignorePackages.isEmpty()) {
254             for (final ExtendedStackTraceElement element : extStackTrace) {
255                 this.formatEntry(element, sb, prefix, textRenderer, suffix, lineSeparator);
256             }
257         } else {
258             int count = 0;
259             for (int i = 0; i < extStackTrace.length; ++i) {
260                 if (!this.ignoreElement(causedTrace[i], ignorePackages)) {
261                     if (count > 0) {
262                         appendSuppressedCount(sb, prefix, count, textRenderer, suffix, lineSeparator);
263                         count = 0;
264                     }
265                     this.formatEntry(extStackTrace[i], sb, prefix, textRenderer, suffix, lineSeparator);
266                 } else {
267                     ++count;
268                 }
269             }
270             if (count > 0) {
271                 appendSuppressedCount(sb, prefix, count, textRenderer, suffix, lineSeparator);
272             }
273         }
274         if (commonCount != 0) {
275             textRenderer.render(prefix, sb, "Prefix");
276             textRenderer.render("\t... ", sb, "More");
277             textRenderer.render(Integer.toString(commonCount), sb, "More");
278             textRenderer.render(" more", sb, "More");
279             renderSuffix(suffix, sb, textRenderer);
280             textRenderer.render(lineSeparator, sb, "Text");
281         }
282     }
283 
284     private void renderSuffix(final String suffix, final StringBuilder sb, final TextRenderer textRenderer) {
285         if (!suffix.isEmpty()) {
286             textRenderer.render(" ", sb, "Suffix");
287             textRenderer.render(suffix, sb, "Suffix");
288         }
289     }
290 
291     private void appendSuppressedCount(final StringBuilder sb, final String prefix, final int count,
292                                        final TextRenderer textRenderer, final String suffix, String lineSeparator) {
293         textRenderer.render(prefix, sb, "Prefix");
294         if (count == 1) {
295             textRenderer.render("\t... ", sb, "Suppressed");
296         } else {
297             textRenderer.render("\t... suppressed ", sb, "Suppressed");
298             textRenderer.render(Integer.toString(count), sb, "Suppressed");
299             textRenderer.render(" lines", sb, "Suppressed");
300         }
301         renderSuffix(suffix, sb, textRenderer);
302         textRenderer.render(lineSeparator, sb, "Text");
303     }
304 
305     private void formatEntry(final ExtendedStackTraceElement extStackTraceElement, final StringBuilder sb,
306                              final String prefix, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
307         textRenderer.render(prefix, sb, "Prefix");
308         textRenderer.render("\tat ", sb, "At");
309         extStackTraceElement.renderOn(sb, textRenderer);
310         renderSuffix(suffix, sb, textRenderer);
311         textRenderer.render(lineSeparator, sb, "Text");
312     }
313 
314     /**
315      * Formats the specified Throwable.
316      *  @param sb    StringBuilder to contain the formatted Throwable.
317      * @param cause The Throwable to format.
318      * @param suffix
319      */
320     public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final String suffix) {
321         this.formatWrapper(sb, cause, null, PlainTextRenderer.getInstance(), suffix);
322     }
323 
324     /**
325      * Formats the specified Throwable.
326      *  @param sb             StringBuilder to contain the formatted Throwable.
327      * @param cause          The Throwable to format.
328      * @param ignorePackages The List of packages to be suppressed from the trace.
329      * @param suffix
330      */
331     @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
332     public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages, final String suffix) {
333         this.formatWrapper(sb, cause, ignorePackages, PlainTextRenderer.getInstance(), suffix);
334     }
335 
336     /**
337      * Formats the specified Throwable.
338      * @param sb StringBuilder to contain the formatted Throwable.
339      * @param cause The Throwable to format.
340      * @param ignorePackages The List of packages to be suppressed from the stack trace.
341      * @param textRenderer The text renderer.
342      * @param suffix Append this to the end of each stack frame.
343      */
344     @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
345     public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages,
346             final TextRenderer textRenderer, final String suffix) {
347         formatWrapper(sb, cause, ignorePackages, textRenderer, suffix, EOL_STR);
348     }
349 
350     /**
351      * Formats the specified Throwable.
352      * @param sb StringBuilder to contain the formatted Throwable.
353      * @param cause The Throwable to format.
354      * @param ignorePackages The List of packages to be suppressed from the stack trace.
355      * @param textRenderer The text renderer.
356      * @param suffix Append this to the end of each stack frame.
357      * @param lineSeparator The end-of-line separator.
358      */
359     @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
360     public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages,
361                               final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
362         final Throwable caused = cause.getCauseProxy() != null ? cause.getCauseProxy().getThrowable() : null;
363         if (caused != null) {
364             this.formatWrapper(sb, cause.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator);
365             sb.append(WRAPPED_BY_LABEL);
366             renderSuffix(suffix, sb, textRenderer);
367         }
368         cause.renderOn(sb, textRenderer);
369         renderSuffix(suffix, sb, textRenderer);
370         textRenderer.render(lineSeparator, sb, "Text");
371         this.formatElements(sb, Strings.EMPTY, cause.commonElementCount,
372             cause.getThrowable().getStackTrace(), cause.extendedStackTrace, ignorePackages, textRenderer, suffix, lineSeparator);
373     }
374 
375     public ThrowableProxy getCauseProxy() {
376         return this.causeProxy;
377     }
378 
379     /**
380      * Formats the Throwable that is the cause of this Throwable.
381      *
382      * @return The formatted Throwable that caused this Throwable.
383      * @param suffix
384      */
385     public String getCauseStackTraceAsString(final String suffix) {
386         return this.getCauseStackTraceAsString(null, PlainTextRenderer.getInstance(), suffix, EOL_STR);
387     }
388 
389     /**
390      * Formats the Throwable that is the cause of this Throwable.
391      *
392      * @param packages The List of packages to be suppressed from the trace.
393      * @param suffix Append this to the end of each stack frame.
394      * @return The formatted Throwable that caused this Throwable.
395      */
396     public String getCauseStackTraceAsString(final List<String> packages, final String suffix) {
397         return getCauseStackTraceAsString(packages, PlainTextRenderer.getInstance(), suffix, EOL_STR);
398     }
399 
400     /**
401      * Formats the Throwable that is the cause of this Throwable.
402      *
403      * @param ignorePackages The List of packages to be suppressed from the trace.
404      * @param textRenderer The text renderer.
405      * @param suffix Append this to the end of each stack frame.
406      * @return The formatted Throwable that caused this Throwable.
407      */
408     public String getCauseStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix) {
409         return getCauseStackTraceAsString(ignorePackages, textRenderer, suffix, EOL_STR);
410     }
411 
412     /**
413      * Formats the Throwable that is the cause of this Throwable.
414      *
415      * @param ignorePackages The List of packages to be suppressed from the stack trace.
416      * @param textRenderer The text renderer.
417      * @param suffix Append this to the end of each stack frame.
418      * @param lineSeparator The end-of-line separator.
419      * @return The formatted Throwable that caused this Throwable.
420      */
421     public String getCauseStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
422         final StringBuilder sb = new StringBuilder();
423         if (this.causeProxy != null) {
424             this.formatWrapper(sb, this.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator);
425             sb.append(WRAPPED_BY_LABEL);
426             renderSuffix(suffix, sb, textRenderer);
427         }
428         this.renderOn(sb, textRenderer);
429         renderSuffix(suffix, sb, textRenderer);
430         textRenderer.render(lineSeparator, sb, "Text");
431         this.formatElements(sb, Strings.EMPTY, 0, this.throwable.getStackTrace(), this.extendedStackTrace,
432             ignorePackages, textRenderer, suffix, lineSeparator);
433         return sb.toString();
434     }
435 
436     /**
437      * Returns the number of elements that are being omitted because they are common with the parent Throwable's stack
438      * trace.
439      *
440      * @return The number of elements omitted from the stack trace.
441      */
442     public int getCommonElementCount() {
443         return this.commonElementCount;
444     }
445 
446     /**
447      * Gets the stack trace including packaging information.
448      *
449      * @return The stack trace including packaging information.
450      */
451     public ExtendedStackTraceElement[] getExtendedStackTrace() {
452         return this.extendedStackTrace;
453     }
454 
455     /**
456      * Formats the stack trace including packaging information.
457      *
458      * @return The formatted stack trace including packaging information.
459      */
460     public String getExtendedStackTraceAsString() {
461         return this.getExtendedStackTraceAsString(null, PlainTextRenderer.getInstance(), Strings.EMPTY, EOL_STR);
462     }
463 
464     /**
465      * Formats the stack trace including packaging information.
466      *
467      * @return The formatted stack trace including packaging information.
468      * @param suffix Append this to the end of each stack frame.
469      */
470     public String getExtendedStackTraceAsString(final String suffix) {
471         return this.getExtendedStackTraceAsString(null, PlainTextRenderer.getInstance(), suffix, EOL_STR);
472     }
473 
474     /**
475      * Formats the stack trace including packaging information.
476      *
477      * @param ignorePackages List of packages to be ignored in the trace.
478      * @param suffix Append this to the end of each stack frame.
479      * @return The formatted stack trace including packaging information.
480      */
481     public String getExtendedStackTraceAsString(final List<String> ignorePackages, final String suffix) {
482         return getExtendedStackTraceAsString(ignorePackages, PlainTextRenderer.getInstance(), suffix, EOL_STR);
483     }
484 
485     /**
486      * Formats the stack trace including packaging information.
487      *
488      * @param ignorePackages List of packages to be ignored in the trace.
489      * @param textRenderer The message renderer.
490      * @param suffix Append this to the end of each stack frame.
491      * @return The formatted stack trace including packaging information.
492      */
493     public String getExtendedStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix) {
494         return getExtendedStackTraceAsString(ignorePackages, textRenderer, suffix, EOL_STR);
495     }
496 
497     /**
498      * Formats the stack trace including packaging information.
499      *
500      * @param ignorePackages List of packages to be ignored in the trace.
501      * @param textRenderer The message renderer.
502      * @param suffix Append this to the end of each stack frame.
503      * @param lineSeparator The end-of-line separator.
504      * @return The formatted stack trace including packaging information.
505      */
506     public String getExtendedStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
507         final StringBuilder sb = new StringBuilder(1024);
508         textRenderer.render(name, sb, "Name");
509         textRenderer.render(": ", sb, "NameMessageSeparator");
510         textRenderer.render(this.message, sb, "Message");
511         renderSuffix(suffix, sb, textRenderer);
512         textRenderer.render(lineSeparator, sb, "Text");
513         final StackTraceElement[] causedTrace = this.throwable != null ? this.throwable.getStackTrace() : null;
514         this.formatElements(sb, Strings.EMPTY, 0, causedTrace, this.extendedStackTrace, ignorePackages, textRenderer, suffix, lineSeparator);
515         this.formatSuppressed(sb, TAB, this.suppressedProxies, ignorePackages, textRenderer, suffix, lineSeparator);
516         this.formatCause(sb, Strings.EMPTY, this.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator);
517         return sb.toString();
518     }
519 
520     public String getLocalizedMessage() {
521         return this.localizedMessage;
522     }
523 
524     public String getMessage() {
525         return this.message;
526     }
527 
528     /**
529      * Return the FQCN of the Throwable.
530      *
531      * @return The FQCN of the Throwable.
532      */
533     public String getName() {
534         return this.name;
535     }
536 
537     public StackTraceElement[] getStackTrace() {
538         return this.throwable == null ? null : this.throwable.getStackTrace();
539     }
540 
541     /**
542      * Gets proxies for suppressed exceptions.
543      *
544      * @return proxies for suppressed exceptions.
545      */
546     public ThrowableProxy[] getSuppressedProxies() {
547         return this.suppressedProxies;
548     }
549 
550     /**
551      * Formats the suppressed Throwables.
552      *
553      * @return The formatted suppressed Throwables.
554      * @param suffix
555      */
556     public String getSuppressedStackTrace(final String suffix) {
557         final ThrowableProxy[] suppressed = this.getSuppressedProxies();
558         if (suppressed == null || suppressed.length == 0) {
559             return Strings.EMPTY;
560         }
561         final StringBuilder sb = new StringBuilder("Suppressed Stack Trace Elements:").append(EOL);
562         for (final ThrowableProxy proxy : suppressed) {
563             sb.append(proxy.getExtendedStackTraceAsString(suffix));
564         }
565         return sb.toString();
566     }
567 
568     /**
569      * The throwable or null if this object is deserialized from XML or JSON.
570      *
571      * @return The throwable or null if this object is deserialized from XML or JSON.
572      */
573     public Throwable getThrowable() {
574         return this.throwable;
575     }
576 
577     @Override
578     public int hashCode() {
579         final int prime = 31;
580         int result = 1;
581         result = prime * result + (this.causeProxy == null ? 0 : this.causeProxy.hashCode());
582         result = prime * result + this.commonElementCount;
583         result = prime * result + (this.extendedStackTrace == null ? 0 : Arrays.hashCode(this.extendedStackTrace));
584         result = prime * result + (this.suppressedProxies == null ? 0 : Arrays.hashCode(this.suppressedProxies));
585         result = prime * result + (this.name == null ? 0 : this.name.hashCode());
586         return result;
587     }
588 
589     private boolean ignoreElement(final StackTraceElement element, final List<String> ignorePackages) {
590         if (ignorePackages != null) {
591             final String className = element.getClassName();
592             for (final String pkg : ignorePackages) {
593                 if (className.startsWith(pkg)) {
594                     return true;
595                 }
596             }
597         }
598         return false;
599     }
600 
601     /**
602      * Loads classes not located via Reflection.getCallerClass.
603      *
604      * @param lastLoader The ClassLoader that loaded the Class that called this Class.
605      * @param className  The name of the Class.
606      * @return The Class object for the Class or null if it could not be located.
607      */
608     private Class<?> loadClass(final ClassLoader lastLoader, final String className) {
609         // XXX: this is overly complicated
610         Class<?> clazz;
611         if (lastLoader != null) {
612             try {
613                 clazz = lastLoader.loadClass(className);
614                 if (clazz != null) {
615                     return clazz;
616                 }
617             } catch (final Throwable ignore) {
618                 // Ignore exception.
619             }
620         }
621         try {
622             clazz = LoaderUtil.loadClass(className);
623         } catch (final ClassNotFoundException | NoClassDefFoundError e) {
624             return loadClass(className);
625         } catch (final SecurityException e) {
626             return null;
627         }
628         return clazz;
629     }
630 
631     private Class<?> loadClass(final String className) {
632         try {
633             return Loader.loadClass(className, this.getClass().getClassLoader());
634         } catch (final ClassNotFoundException | NoClassDefFoundError | SecurityException e) {
635             return null;
636         }
637     }
638 
639     /**
640      * Construct the CacheEntry from the Class's information.
641      *
642      * @param stackTraceElement The stack trace element
643      * @param callerClass       The Class.
644      * @param exact             True if the class was obtained via Reflection.getCallerClass.
645      * @return The CacheEntry.
646      */
647     private CacheEntry toCacheEntry(final StackTraceElement stackTraceElement, final Class<?> callerClass,
648                                     final boolean exact) {
649         String location = "?";
650         String version = "?";
651         ClassLoader lastLoader = null;
652         if (callerClass != null) {
653             try {
654                 final CodeSource source = callerClass.getProtectionDomain().getCodeSource();
655                 if (source != null) {
656                     final URL locationURL = source.getLocation();
657                     if (locationURL != null) {
658                         final String str = locationURL.toString().replace('\\', '/');
659                         int index = str.lastIndexOf("/");
660                         if (index >= 0 && index == str.length() - 1) {
661                             index = str.lastIndexOf("/", index - 1);
662                             location = str.substring(index + 1);
663                         } else {
664                             location = str.substring(index + 1);
665                         }
666                     }
667                 }
668             } catch (final Exception ex) {
669                 // Ignore the exception.
670             }
671             final Package pkg = callerClass.getPackage();
672             if (pkg != null) {
673                 final String ver = pkg.getImplementationVersion();
674                 if (ver != null) {
675                     version = ver;
676                 }
677             }
678             try {
679                 lastLoader = callerClass.getClassLoader();
680             } catch (final SecurityException e) {
681                 lastLoader = null;
682             }
683         }
684         return new CacheEntry(new ExtendedClassInfo(exact, location, version), lastLoader);
685     }
686 
687     /**
688      * Resolve all the stack entries in this stack trace that are not common with the parent.
689      *
690      * @param stack      The callers Class stack.
691      * @param map        The cache of CacheEntry objects.
692      * @param rootTrace  The first stack trace resolve or null.
693      * @param stackTrace The stack trace being resolved.
694      * @return The StackTracePackageElement array.
695      */
696     ExtendedStackTraceElement[] toExtendedStackTrace(final Stack<Class<?>> stack, final Map<String, CacheEntry> map,
697                                                      final StackTraceElement[] rootTrace,
698                                                      final StackTraceElement[] stackTrace) {
699         int stackLength;
700         if (rootTrace != null) {
701             int rootIndex = rootTrace.length - 1;
702             int stackIndex = stackTrace.length - 1;
703             while (rootIndex >= 0 && stackIndex >= 0 && rootTrace[rootIndex].equals(stackTrace[stackIndex])) {
704                 --rootIndex;
705                 --stackIndex;
706             }
707             this.commonElementCount = stackTrace.length - 1 - stackIndex;
708             stackLength = stackIndex + 1;
709         } else {
710             this.commonElementCount = 0;
711             stackLength = stackTrace.length;
712         }
713         final ExtendedStackTraceElement[] extStackTrace = new ExtendedStackTraceElement[stackLength];
714         Class<?> clazz = stack.isEmpty() ? null : stack.peek();
715         ClassLoader lastLoader = null;
716         for (int i = stackLength - 1; i >= 0; --i) {
717             final StackTraceElement stackTraceElement = stackTrace[i];
718             final String className = stackTraceElement.getClassName();
719             // The stack returned from getCurrentStack may be missing entries for java.lang.reflect.Method.invoke()
720             // and its implementation. The Throwable might also contain stack entries that are no longer
721             // present as those methods have returned.
722             ExtendedClassInfo extClassInfo;
723             if (clazz != null && className.equals(clazz.getName())) {
724                 final CacheEntry entry = this.toCacheEntry(stackTraceElement, clazz, true);
725                 extClassInfo = entry.element;
726                 lastLoader = entry.loader;
727                 stack.pop();
728                 clazz = stack.isEmpty() ? null : stack.peek();
729             } else {
730                 final CacheEntry cacheEntry = map.get(className);
731                 if (cacheEntry != null) {
732                     final CacheEntry entry = cacheEntry;
733                     extClassInfo = entry.element;
734                     if (entry.loader != null) {
735                         lastLoader = entry.loader;
736                     }
737                 } else {
738                     final CacheEntry entry = this.toCacheEntry(stackTraceElement,
739                         this.loadClass(lastLoader, className), false);
740                     extClassInfo = entry.element;
741                     map.put(className, entry);
742                     if (entry.loader != null) {
743                         lastLoader = entry.loader;
744                     }
745                 }
746             }
747             extStackTrace[i] = new ExtendedStackTraceElement(stackTraceElement, extClassInfo);
748         }
749         return extStackTrace;
750     }
751 
752     @Override
753     public String toString() {
754         final String msg = this.message;
755         return msg != null ? this.name + ": " + msg : this.name;
756     }
757 
758     private ThrowableProxy[] toSuppressedProxies(final Throwable thrown, Set<Throwable> suppressedVisited) {
759         try {
760             final Throwable[] suppressed = thrown.getSuppressed();
761             if (suppressed == null) {
762                 return EMPTY_THROWABLE_PROXY_ARRAY;
763             }
764             final List<ThrowableProxy> proxies = new ArrayList<>(suppressed.length);
765             if (suppressedVisited == null) {
766                 suppressedVisited = new HashSet<>(proxies.size());
767             }
768             for (int i = 0; i < suppressed.length; i++) {
769                 final Throwable candidate = suppressed[i];
770                 if (!suppressedVisited.contains(candidate)) {
771                     suppressedVisited.add(candidate);
772                     proxies.add(new ThrowableProxy(candidate, suppressedVisited));
773                 }
774             }
775             return proxies.toArray(new ThrowableProxy[proxies.size()]);
776         } catch (final Exception e) {
777             StatusLogger.getLogger().error(e);
778         }
779         return null;
780     }
781 }