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.ReflectionUtil;
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 = ReflectionUtil.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) {
211         formatThrowableProxy(sb, prefix, CAUSED_BY_LABEL, cause, ignorePackages, textRenderer, suffix);
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) {
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(EOL_STR, sb, "Text");
225         this.formatElements(sb, prefix, throwableProxy.commonElementCount,
226             throwableProxy.getStackTrace(), throwableProxy.extendedStackTrace, ignorePackages, textRenderer, suffix);
227         this.formatSuppressed(sb, prefix + TAB, throwableProxy.suppressedProxies, ignorePackages, textRenderer, suffix);
228         this.formatCause(sb, prefix, throwableProxy.causeProxy, ignorePackages, textRenderer, suffix);
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) {
242         if (suppressedProxies == null) {
243             return;
244         }
245         for (final ThrowableProxy suppressedProxy : suppressedProxies) {
246             formatThrowableProxy(sb, prefix, SUPPRESSED_LABEL, suppressedProxy, ignorePackages, textRenderer, suffix);
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) {
253         if (ignorePackages == null || ignorePackages.isEmpty()) {
254             for (final ExtendedStackTraceElement element : extStackTrace) {
255                 this.formatEntry(element, sb, prefix, textRenderer, suffix);
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);
263                         count = 0;
264                     }
265                     this.formatEntry(extStackTrace[i], sb, prefix, textRenderer, suffix);
266                 } else {
267                     ++count;
268                 }
269             }
270             if (count > 0) {
271                 appendSuppressedCount(sb, prefix, count, textRenderer, suffix);
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(EOL_STR, 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) {
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(EOL_STR, sb, "Text");
303     }
304 
305     private void formatEntry(final ExtendedStackTraceElement extStackTraceElement, final StringBuilder sb,
306                              final String prefix, final TextRenderer textRenderer, final String suffix) {
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(EOL_STR, 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 trace.
341      * @param textRenderer   The text render
342      * @param suffix
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         final Throwable caused = cause.getCauseProxy() != null ? cause.getCauseProxy().getThrowable() : null;
348         if (caused != null) {
349             this.formatWrapper(sb, cause.causeProxy, ignorePackages, textRenderer, suffix);
350             sb.append(WRAPPED_BY_LABEL);
351             renderSuffix(suffix, sb, textRenderer);
352         }
353         cause.renderOn(sb, textRenderer);
354         renderSuffix(suffix, sb, textRenderer);
355         textRenderer.render(EOL_STR, sb, "Text");
356         this.formatElements(sb, Strings.EMPTY, cause.commonElementCount,
357             cause.getThrowable().getStackTrace(), cause.extendedStackTrace, ignorePackages, textRenderer, suffix);
358     }
359 
360     public ThrowableProxy getCauseProxy() {
361         return this.causeProxy;
362     }
363 
364     /**
365      * Formats the Throwable that is the cause of this Throwable.
366      *
367      * @return The formatted Throwable that caused this Throwable.
368      * @param suffix
369      */
370     public String getCauseStackTraceAsString(final String suffix) {
371         return this.getCauseStackTraceAsString(null, PlainTextRenderer.getInstance(), suffix);
372     }
373 
374     /**
375      * Formats the Throwable that is the cause of this Throwable.
376      *
377      * @param packages The List of packages to be suppressed from the trace.
378      * @param suffix
379      * @return The formatted Throwable that caused this Throwable.
380      */
381     public String getCauseStackTraceAsString(final List<String> packages, final String suffix) {
382         return getCauseStackTraceAsString(packages, PlainTextRenderer.getInstance(), suffix);
383     }
384 
385     /**
386      * Formats the Throwable that is the cause of this Throwable.
387      *
388      * @param ignorePackages The List of packages to be suppressed from the trace.
389      * @param textRenderer   the text renderer
390      * @param suffix
391      * @return The formatted Throwable that caused this Throwable.
392      */
393     public String getCauseStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix) {
394         final StringBuilder sb = new StringBuilder();
395         if (this.causeProxy != null) {
396             this.formatWrapper(sb, this.causeProxy, ignorePackages, textRenderer, suffix);
397             sb.append(WRAPPED_BY_LABEL);
398             renderSuffix(suffix, sb, textRenderer);
399         }
400         this.renderOn(sb, textRenderer);
401         renderSuffix(suffix, sb, textRenderer);
402         textRenderer.render(EOL_STR, sb, "Text");
403         this.formatElements(sb, Strings.EMPTY, 0, this.throwable.getStackTrace(), this.extendedStackTrace,
404             ignorePackages, textRenderer, suffix);
405         return sb.toString();
406     }
407 
408     /**
409      * Returns the number of elements that are being omitted because they are common with the parent Throwable's stack
410      * trace.
411      *
412      * @return The number of elements omitted from the stack trace.
413      */
414     public int getCommonElementCount() {
415         return this.commonElementCount;
416     }
417 
418     /**
419      * Gets the stack trace including packaging information.
420      *
421      * @return The stack trace including packaging information.
422      */
423     public ExtendedStackTraceElement[] getExtendedStackTrace() {
424         return this.extendedStackTrace;
425     }
426 
427     /**
428      * Format the stack trace including packaging information.
429      *
430      * @return The formatted stack trace including packaging information.
431      * @param suffix
432      */
433     public String getExtendedStackTraceAsString(final String suffix) {
434         return this.getExtendedStackTraceAsString(null, PlainTextRenderer.getInstance(), suffix);
435     }
436 
437     /**
438      * Format the stack trace including packaging information.
439      *
440      * @param ignorePackages List of packages to be ignored in the trace.
441      * @param suffix
442      * @return The formatted stack trace including packaging information.
443      */
444     public String getExtendedStackTraceAsString(final List<String> ignorePackages, final String suffix) {
445         return getExtendedStackTraceAsString(ignorePackages, PlainTextRenderer.getInstance(), suffix);
446     }
447 
448     /**
449      * Format the stack trace including packaging information.
450      *
451      * @param ignorePackages List of packages to be ignored in the trace.
452      * @param textRenderer   The message renderer
453      * @param suffix
454      * @return The formatted stack trace including packaging information.
455      */
456     public String getExtendedStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix) {
457         final StringBuilder sb = new StringBuilder(1024);
458         textRenderer.render(name, sb, "Name");
459         textRenderer.render(": ", sb, "NameMessageSeparator");
460         textRenderer.render(this.message, sb, "Message");
461         renderSuffix(suffix, sb, textRenderer);
462         textRenderer.render(EOL_STR, sb, "Text");
463         final StackTraceElement[] causedTrace = this.throwable != null ? this.throwable.getStackTrace() : null;
464         this.formatElements(sb, Strings.EMPTY, 0, causedTrace, this.extendedStackTrace, ignorePackages, textRenderer, suffix);
465         this.formatSuppressed(sb, TAB, this.suppressedProxies, ignorePackages, textRenderer, suffix);
466         this.formatCause(sb, Strings.EMPTY, this.causeProxy, ignorePackages, textRenderer, suffix);
467         return sb.toString();
468     }
469 
470     public String getLocalizedMessage() {
471         return this.localizedMessage;
472     }
473 
474     public String getMessage() {
475         return this.message;
476     }
477 
478     /**
479      * Return the FQCN of the Throwable.
480      *
481      * @return The FQCN of the Throwable.
482      */
483     public String getName() {
484         return this.name;
485     }
486 
487     public StackTraceElement[] getStackTrace() {
488         return this.throwable == null ? null : this.throwable.getStackTrace();
489     }
490 
491     /**
492      * Gets proxies for suppressed exceptions.
493      *
494      * @return proxies for suppressed exceptions.
495      */
496     public ThrowableProxy[] getSuppressedProxies() {
497         return this.suppressedProxies;
498     }
499 
500     /**
501      * Format the suppressed Throwables.
502      *
503      * @return The formatted suppressed Throwables.
504      * @param suffix
505      */
506     public String getSuppressedStackTrace(final String suffix) {
507         final ThrowableProxy[] suppressed = this.getSuppressedProxies();
508         if (suppressed == null || suppressed.length == 0) {
509             return Strings.EMPTY;
510         }
511         final StringBuilder sb = new StringBuilder("Suppressed Stack Trace Elements:").append(EOL);
512         for (final ThrowableProxy proxy : suppressed) {
513             sb.append(proxy.getExtendedStackTraceAsString(suffix));
514         }
515         return sb.toString();
516     }
517 
518     /**
519      * The throwable or null if this object is deserialized from XML or JSON.
520      *
521      * @return The throwable or null if this object is deserialized from XML or JSON.
522      */
523     public Throwable getThrowable() {
524         return this.throwable;
525     }
526 
527     @Override
528     public int hashCode() {
529         final int prime = 31;
530         int result = 1;
531         result = prime * result + (this.causeProxy == null ? 0 : this.causeProxy.hashCode());
532         result = prime * result + this.commonElementCount;
533         result = prime * result + (this.extendedStackTrace == null ? 0 : Arrays.hashCode(this.extendedStackTrace));
534         result = prime * result + (this.suppressedProxies == null ? 0 : Arrays.hashCode(this.suppressedProxies));
535         result = prime * result + (this.name == null ? 0 : this.name.hashCode());
536         return result;
537     }
538 
539     private boolean ignoreElement(final StackTraceElement element, final List<String> ignorePackages) {
540         if (ignorePackages != null) {
541             final String className = element.getClassName();
542             for (final String pkg : ignorePackages) {
543                 if (className.startsWith(pkg)) {
544                     return true;
545                 }
546             }
547         }
548         return false;
549     }
550 
551     /**
552      * Loads classes not located via Reflection.getCallerClass.
553      *
554      * @param lastLoader The ClassLoader that loaded the Class that called this Class.
555      * @param className  The name of the Class.
556      * @return The Class object for the Class or null if it could not be located.
557      */
558     private Class<?> loadClass(final ClassLoader lastLoader, final String className) {
559         // XXX: this is overly complicated
560         Class<?> clazz;
561         if (lastLoader != null) {
562             try {
563                 clazz = lastLoader.loadClass(className);
564                 if (clazz != null) {
565                     return clazz;
566                 }
567             } catch (final Throwable ignore) {
568                 // Ignore exception.
569             }
570         }
571         try {
572             clazz = LoaderUtil.loadClass(className);
573         } catch (final ClassNotFoundException | NoClassDefFoundError e) {
574             return loadClass(className);
575         } catch (final SecurityException e) {
576             return null;
577         }
578         return clazz;
579     }
580 
581     private Class<?> loadClass(final String className) {
582         try {
583             return Loader.loadClass(className, this.getClass().getClassLoader());
584         } catch (final ClassNotFoundException | NoClassDefFoundError | SecurityException e) {
585             return null;
586         }
587     }
588 
589     /**
590      * Construct the CacheEntry from the Class's information.
591      *
592      * @param stackTraceElement The stack trace element
593      * @param callerClass       The Class.
594      * @param exact             True if the class was obtained via Reflection.getCallerClass.
595      * @return The CacheEntry.
596      */
597     private CacheEntry toCacheEntry(final StackTraceElement stackTraceElement, final Class<?> callerClass,
598                                     final boolean exact) {
599         String location = "?";
600         String version = "?";
601         ClassLoader lastLoader = null;
602         if (callerClass != null) {
603             try {
604                 final CodeSource source = callerClass.getProtectionDomain().getCodeSource();
605                 if (source != null) {
606                     final URL locationURL = source.getLocation();
607                     if (locationURL != null) {
608                         final String str = locationURL.toString().replace('\\', '/');
609                         int index = str.lastIndexOf("/");
610                         if (index >= 0 && index == str.length() - 1) {
611                             index = str.lastIndexOf("/", index - 1);
612                             location = str.substring(index + 1);
613                         } else {
614                             location = str.substring(index + 1);
615                         }
616                     }
617                 }
618             } catch (final Exception ex) {
619                 // Ignore the exception.
620             }
621             final Package pkg = callerClass.getPackage();
622             if (pkg != null) {
623                 final String ver = pkg.getImplementationVersion();
624                 if (ver != null) {
625                     version = ver;
626                 }
627             }
628             try {
629                 lastLoader = callerClass.getClassLoader();
630             } catch (final SecurityException e) {
631                 lastLoader = null;
632             }
633         }
634         return new CacheEntry(new ExtendedClassInfo(exact, location, version), lastLoader);
635     }
636 
637     /**
638      * Resolve all the stack entries in this stack trace that are not common with the parent.
639      *
640      * @param stack      The callers Class stack.
641      * @param map        The cache of CacheEntry objects.
642      * @param rootTrace  The first stack trace resolve or null.
643      * @param stackTrace The stack trace being resolved.
644      * @return The StackTracePackageElement array.
645      */
646     ExtendedStackTraceElement[] toExtendedStackTrace(final Stack<Class<?>> stack, final Map<String, CacheEntry> map,
647                                                      final StackTraceElement[] rootTrace,
648                                                      final StackTraceElement[] stackTrace) {
649         int stackLength;
650         if (rootTrace != null) {
651             int rootIndex = rootTrace.length - 1;
652             int stackIndex = stackTrace.length - 1;
653             while (rootIndex >= 0 && stackIndex >= 0 && rootTrace[rootIndex].equals(stackTrace[stackIndex])) {
654                 --rootIndex;
655                 --stackIndex;
656             }
657             this.commonElementCount = stackTrace.length - 1 - stackIndex;
658             stackLength = stackIndex + 1;
659         } else {
660             this.commonElementCount = 0;
661             stackLength = stackTrace.length;
662         }
663         final ExtendedStackTraceElement[] extStackTrace = new ExtendedStackTraceElement[stackLength];
664         Class<?> clazz = stack.isEmpty() ? null : stack.peek();
665         ClassLoader lastLoader = null;
666         for (int i = stackLength - 1; i >= 0; --i) {
667             final StackTraceElement stackTraceElement = stackTrace[i];
668             final String className = stackTraceElement.getClassName();
669             // The stack returned from getCurrentStack may be missing entries for java.lang.reflect.Method.invoke()
670             // and its implementation. The Throwable might also contain stack entries that are no longer
671             // present as those methods have returned.
672             ExtendedClassInfo extClassInfo;
673             if (clazz != null && className.equals(clazz.getName())) {
674                 final CacheEntry entry = this.toCacheEntry(stackTraceElement, clazz, true);
675                 extClassInfo = entry.element;
676                 lastLoader = entry.loader;
677                 stack.pop();
678                 clazz = stack.isEmpty() ? null : stack.peek();
679             } else {
680                 final CacheEntry cacheEntry = map.get(className);
681                 if (cacheEntry != null) {
682                     final CacheEntry entry = cacheEntry;
683                     extClassInfo = entry.element;
684                     if (entry.loader != null) {
685                         lastLoader = entry.loader;
686                     }
687                 } else {
688                     final CacheEntry entry = this.toCacheEntry(stackTraceElement,
689                         this.loadClass(lastLoader, className), false);
690                     extClassInfo = entry.element;
691                     map.put(stackTraceElement.toString(), entry);
692                     if (entry.loader != null) {
693                         lastLoader = entry.loader;
694                     }
695                 }
696             }
697             extStackTrace[i] = new ExtendedStackTraceElement(stackTraceElement, extClassInfo);
698         }
699         return extStackTrace;
700     }
701 
702     @Override
703     public String toString() {
704         final String msg = this.message;
705         return msg != null ? this.name + ": " + msg : this.name;
706     }
707 
708     private ThrowableProxy[] toSuppressedProxies(final Throwable thrown, Set<Throwable> suppressedVisited) {
709         try {
710             final Throwable[] suppressed = thrown.getSuppressed();
711             if (suppressed == null) {
712                 return EMPTY_THROWABLE_PROXY_ARRAY;
713             }
714             final List<ThrowableProxy> proxies = new ArrayList<>(suppressed.length);
715             if (suppressedVisited == null) {
716                 suppressedVisited = new HashSet<>(proxies.size());
717             }
718             for (int i = 0; i < suppressed.length; i++) {
719                 final Throwable candidate = suppressed[i];
720                 if (!suppressedVisited.contains(candidate)) {
721                     suppressedVisited.add(candidate);
722                     proxies.add(new ThrowableProxy(candidate, suppressedVisited));
723                 }
724             }
725             return proxies.toArray(new ThrowableProxy[proxies.size()]);
726         } catch (final Exception e) {
727             StatusLogger.getLogger().error(e);
728         }
729         return null;
730     }
731 }