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) {
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() {
434         return this.getExtendedStackTraceAsString(null, PlainTextRenderer.getInstance(), "");
435     }
436 
437     /**
438      * Format the stack trace including packaging information.
439      *
440      * @return The formatted stack trace including packaging information.
441      * @param suffix
442      */
443     public String getExtendedStackTraceAsString(final String suffix) {
444         return this.getExtendedStackTraceAsString(null, PlainTextRenderer.getInstance(), suffix);
445     }
446 
447     /**
448      * Format the stack trace including packaging information.
449      *
450      * @param ignorePackages List of packages to be ignored in the trace.
451      * @param suffix
452      * @return The formatted stack trace including packaging information.
453      */
454     public String getExtendedStackTraceAsString(final List<String> ignorePackages, final String suffix) {
455         return getExtendedStackTraceAsString(ignorePackages, PlainTextRenderer.getInstance(), suffix);
456     }
457 
458     /**
459      * Format the stack trace including packaging information.
460      *
461      * @param ignorePackages List of packages to be ignored in the trace.
462      * @param textRenderer   The message renderer
463      * @param suffix
464      * @return The formatted stack trace including packaging information.
465      */
466     public String getExtendedStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix) {
467         final StringBuilder sb = new StringBuilder(1024);
468         textRenderer.render(name, sb, "Name");
469         textRenderer.render(": ", sb, "NameMessageSeparator");
470         textRenderer.render(this.message, sb, "Message");
471         renderSuffix(suffix, sb, textRenderer);
472         textRenderer.render(EOL_STR, sb, "Text");
473         final StackTraceElement[] causedTrace = this.throwable != null ? this.throwable.getStackTrace() : null;
474         this.formatElements(sb, Strings.EMPTY, 0, causedTrace, this.extendedStackTrace, ignorePackages, textRenderer, suffix);
475         this.formatSuppressed(sb, TAB, this.suppressedProxies, ignorePackages, textRenderer, suffix);
476         this.formatCause(sb, Strings.EMPTY, this.causeProxy, ignorePackages, textRenderer, suffix);
477         return sb.toString();
478     }
479 
480     public String getLocalizedMessage() {
481         return this.localizedMessage;
482     }
483 
484     public String getMessage() {
485         return this.message;
486     }
487 
488     /**
489      * Return the FQCN of the Throwable.
490      *
491      * @return The FQCN of the Throwable.
492      */
493     public String getName() {
494         return this.name;
495     }
496 
497     public StackTraceElement[] getStackTrace() {
498         return this.throwable == null ? null : this.throwable.getStackTrace();
499     }
500 
501     /**
502      * Gets proxies for suppressed exceptions.
503      *
504      * @return proxies for suppressed exceptions.
505      */
506     public ThrowableProxy[] getSuppressedProxies() {
507         return this.suppressedProxies;
508     }
509 
510     /**
511      * Format the suppressed Throwables.
512      *
513      * @return The formatted suppressed Throwables.
514      * @param suffix
515      */
516     public String getSuppressedStackTrace(final String suffix) {
517         final ThrowableProxy[] suppressed = this.getSuppressedProxies();
518         if (suppressed == null || suppressed.length == 0) {
519             return Strings.EMPTY;
520         }
521         final StringBuilder sb = new StringBuilder("Suppressed Stack Trace Elements:").append(EOL);
522         for (final ThrowableProxy proxy : suppressed) {
523             sb.append(proxy.getExtendedStackTraceAsString(suffix));
524         }
525         return sb.toString();
526     }
527 
528     /**
529      * The throwable or null if this object is deserialized from XML or JSON.
530      *
531      * @return The throwable or null if this object is deserialized from XML or JSON.
532      */
533     public Throwable getThrowable() {
534         return this.throwable;
535     }
536 
537     @Override
538     public int hashCode() {
539         final int prime = 31;
540         int result = 1;
541         result = prime * result + (this.causeProxy == null ? 0 : this.causeProxy.hashCode());
542         result = prime * result + this.commonElementCount;
543         result = prime * result + (this.extendedStackTrace == null ? 0 : Arrays.hashCode(this.extendedStackTrace));
544         result = prime * result + (this.suppressedProxies == null ? 0 : Arrays.hashCode(this.suppressedProxies));
545         result = prime * result + (this.name == null ? 0 : this.name.hashCode());
546         return result;
547     }
548 
549     private boolean ignoreElement(final StackTraceElement element, final List<String> ignorePackages) {
550         if (ignorePackages != null) {
551             final String className = element.getClassName();
552             for (final String pkg : ignorePackages) {
553                 if (className.startsWith(pkg)) {
554                     return true;
555                 }
556             }
557         }
558         return false;
559     }
560 
561     /**
562      * Loads classes not located via Reflection.getCallerClass.
563      *
564      * @param lastLoader The ClassLoader that loaded the Class that called this Class.
565      * @param className  The name of the Class.
566      * @return The Class object for the Class or null if it could not be located.
567      */
568     private Class<?> loadClass(final ClassLoader lastLoader, final String className) {
569         // XXX: this is overly complicated
570         Class<?> clazz;
571         if (lastLoader != null) {
572             try {
573                 clazz = lastLoader.loadClass(className);
574                 if (clazz != null) {
575                     return clazz;
576                 }
577             } catch (final Throwable ignore) {
578                 // Ignore exception.
579             }
580         }
581         try {
582             clazz = LoaderUtil.loadClass(className);
583         } catch (final ClassNotFoundException | NoClassDefFoundError e) {
584             return loadClass(className);
585         } catch (final SecurityException e) {
586             return null;
587         }
588         return clazz;
589     }
590 
591     private Class<?> loadClass(final String className) {
592         try {
593             return Loader.loadClass(className, this.getClass().getClassLoader());
594         } catch (final ClassNotFoundException | NoClassDefFoundError | SecurityException e) {
595             return null;
596         }
597     }
598 
599     /**
600      * Construct the CacheEntry from the Class's information.
601      *
602      * @param stackTraceElement The stack trace element
603      * @param callerClass       The Class.
604      * @param exact             True if the class was obtained via Reflection.getCallerClass.
605      * @return The CacheEntry.
606      */
607     private CacheEntry toCacheEntry(final StackTraceElement stackTraceElement, final Class<?> callerClass,
608                                     final boolean exact) {
609         String location = "?";
610         String version = "?";
611         ClassLoader lastLoader = null;
612         if (callerClass != null) {
613             try {
614                 final CodeSource source = callerClass.getProtectionDomain().getCodeSource();
615                 if (source != null) {
616                     final URL locationURL = source.getLocation();
617                     if (locationURL != null) {
618                         final String str = locationURL.toString().replace('\\', '/');
619                         int index = str.lastIndexOf("/");
620                         if (index >= 0 && index == str.length() - 1) {
621                             index = str.lastIndexOf("/", index - 1);
622                             location = str.substring(index + 1);
623                         } else {
624                             location = str.substring(index + 1);
625                         }
626                     }
627                 }
628             } catch (final Exception ex) {
629                 // Ignore the exception.
630             }
631             final Package pkg = callerClass.getPackage();
632             if (pkg != null) {
633                 final String ver = pkg.getImplementationVersion();
634                 if (ver != null) {
635                     version = ver;
636                 }
637             }
638             try {
639                 lastLoader = callerClass.getClassLoader();
640             } catch (final SecurityException e) {
641                 lastLoader = null;
642             }
643         }
644         return new CacheEntry(new ExtendedClassInfo(exact, location, version), lastLoader);
645     }
646 
647     /**
648      * Resolve all the stack entries in this stack trace that are not common with the parent.
649      *
650      * @param stack      The callers Class stack.
651      * @param map        The cache of CacheEntry objects.
652      * @param rootTrace  The first stack trace resolve or null.
653      * @param stackTrace The stack trace being resolved.
654      * @return The StackTracePackageElement array.
655      */
656     ExtendedStackTraceElement[] toExtendedStackTrace(final Stack<Class<?>> stack, final Map<String, CacheEntry> map,
657                                                      final StackTraceElement[] rootTrace,
658                                                      final StackTraceElement[] stackTrace) {
659         int stackLength;
660         if (rootTrace != null) {
661             int rootIndex = rootTrace.length - 1;
662             int stackIndex = stackTrace.length - 1;
663             while (rootIndex >= 0 && stackIndex >= 0 && rootTrace[rootIndex].equals(stackTrace[stackIndex])) {
664                 --rootIndex;
665                 --stackIndex;
666             }
667             this.commonElementCount = stackTrace.length - 1 - stackIndex;
668             stackLength = stackIndex + 1;
669         } else {
670             this.commonElementCount = 0;
671             stackLength = stackTrace.length;
672         }
673         final ExtendedStackTraceElement[] extStackTrace = new ExtendedStackTraceElement[stackLength];
674         Class<?> clazz = stack.isEmpty() ? null : stack.peek();
675         ClassLoader lastLoader = null;
676         for (int i = stackLength - 1; i >= 0; --i) {
677             final StackTraceElement stackTraceElement = stackTrace[i];
678             final String className = stackTraceElement.getClassName();
679             // The stack returned from getCurrentStack may be missing entries for java.lang.reflect.Method.invoke()
680             // and its implementation. The Throwable might also contain stack entries that are no longer
681             // present as those methods have returned.
682             ExtendedClassInfo extClassInfo;
683             if (clazz != null && className.equals(clazz.getName())) {
684                 final CacheEntry entry = this.toCacheEntry(stackTraceElement, clazz, true);
685                 extClassInfo = entry.element;
686                 lastLoader = entry.loader;
687                 stack.pop();
688                 clazz = stack.isEmpty() ? null : stack.peek();
689             } else {
690                 final CacheEntry cacheEntry = map.get(className);
691                 if (cacheEntry != null) {
692                     final CacheEntry entry = cacheEntry;
693                     extClassInfo = entry.element;
694                     if (entry.loader != null) {
695                         lastLoader = entry.loader;
696                     }
697                 } else {
698                     final CacheEntry entry = this.toCacheEntry(stackTraceElement,
699                         this.loadClass(lastLoader, className), false);
700                     extClassInfo = entry.element;
701                     map.put(stackTraceElement.toString(), entry);
702                     if (entry.loader != null) {
703                         lastLoader = entry.loader;
704                     }
705                 }
706             }
707             extStackTrace[i] = new ExtendedStackTraceElement(stackTraceElement, extClassInfo);
708         }
709         return extStackTrace;
710     }
711 
712     @Override
713     public String toString() {
714         final String msg = this.message;
715         return msg != null ? this.name + ": " + msg : this.name;
716     }
717 
718     private ThrowableProxy[] toSuppressedProxies(final Throwable thrown, Set<Throwable> suppressedVisited) {
719         try {
720             final Throwable[] suppressed = thrown.getSuppressed();
721             if (suppressed == null) {
722                 return EMPTY_THROWABLE_PROXY_ARRAY;
723             }
724             final List<ThrowableProxy> proxies = new ArrayList<>(suppressed.length);
725             if (suppressedVisited == null) {
726                 suppressedVisited = new HashSet<>(proxies.size());
727             }
728             for (int i = 0; i < suppressed.length; i++) {
729                 final Throwable candidate = suppressed[i];
730                 if (!suppressedVisited.contains(candidate)) {
731                     suppressedVisited.add(candidate);
732                     proxies.add(new ThrowableProxy(candidate, suppressedVisited));
733                 }
734             }
735             return proxies.toArray(new ThrowableProxy[proxies.size()]);
736         } catch (final Exception e) {
737             StatusLogger.getLogger().error(e);
738         }
739         return null;
740     }
741 }