001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.core.impl;
018
019import java.io.Serializable;
020import java.net.URL;
021import java.security.CodeSource;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029import java.util.Stack;
030
031import org.apache.logging.log4j.core.pattern.PlainTextRenderer;
032import org.apache.logging.log4j.core.pattern.TextRenderer;
033import org.apache.logging.log4j.core.util.Loader;
034import org.apache.logging.log4j.status.StatusLogger;
035import org.apache.logging.log4j.util.LoaderUtil;
036import org.apache.logging.log4j.util.StackLocatorUtil;
037import org.apache.logging.log4j.util.Strings;
038
039/**
040 * Wraps a Throwable to add packaging information about each stack trace element.
041 *
042 * <p>
043 * A proxy is used to represent a throwable that may not exist in a different class loader or JVM. When an application
044 * deserializes a ThrowableProxy, the throwable may not be set, but the throwable's information is preserved in other
045 * fields of the proxy like the message and stack trace.
046 * </p>
047 *
048 * <p>
049 * TODO: Move this class to org.apache.logging.log4j.core because it is used from LogEvent.
050 * </p>
051 * <p>
052 * TODO: Deserialize: Try to rebuild Throwable if the target exception is in this class loader?
053 * </p>
054 */
055public class ThrowableProxy implements Serializable {
056
057    private static final String TAB = "\t";
058    private static final String CAUSED_BY_LABEL = "Caused by: ";
059    private static final String SUPPRESSED_LABEL = "Suppressed: ";
060    private static final String WRAPPED_BY_LABEL = "Wrapped by: ";
061
062    /**
063     * Cached StackTracePackageElement and ClassLoader.
064     * <p>
065     * Consider this class private.
066     * </p>
067     */
068    static class CacheEntry {
069        private final ExtendedClassInfo element;
070        private final ClassLoader loader;
071
072        public CacheEntry(final ExtendedClassInfo element, final ClassLoader loader) {
073            this.element = element;
074            this.loader = loader;
075        }
076    }
077
078    private static final ThrowableProxy[] EMPTY_THROWABLE_PROXY_ARRAY = new ThrowableProxy[0];
079
080    private static final char EOL = '\n';
081
082    private static final String EOL_STR = String.valueOf(EOL);
083
084    private static final long serialVersionUID = -2752771578252251910L;
085
086    private final ThrowableProxy causeProxy;
087
088    private int commonElementCount;
089
090    private final ExtendedStackTraceElement[] extendedStackTrace;
091
092    private final String localizedMessage;
093
094    private final String message;
095
096    private final String name;
097
098    private final ThrowableProxy[] suppressedProxies;
099
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(stackTraceElement.toString(), 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}