001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.classic.spi;
015
016import ch.qos.logback.core.CoreConstants;
017import ch.qos.logback.core.util.OptionHelper;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.IdentityHashMap;
022import java.util.List;
023import java.util.Set;
024
025public class ThrowableProxy implements IThrowableProxy {
026
027    static final StackTraceElementProxy[] EMPTY_STEP = new StackTraceElementProxy[0];
028
029    private Throwable throwable;
030    private String className;
031    private String message;
032    // package-private because of ThrowableProxyUtil
033    StackTraceElementProxy[] stackTraceElementProxyArray;
034    // package-private because of ThrowableProxyUtil
035    int commonFrames;
036    private ThrowableProxy cause;
037    private ThrowableProxy[] suppressed = NO_SUPPRESSED;
038
039    // private final Set<Throwable> alreadyProcessedSet;
040
041    private transient PackagingDataCalculator packagingDataCalculator;
042    private boolean calculatedPackageData = false;
043
044    private boolean circular;
045
046    private static final ThrowableProxy[] NO_SUPPRESSED = new ThrowableProxy[0];
047
048    public ThrowableProxy(Throwable throwable) {
049        // use an identity set to detect cycles in the throwable chain
050        this(throwable, Collections.newSetFromMap(new IdentityHashMap<>()));
051    }
052
053    // used for circular exceptions
054    private ThrowableProxy(Throwable circular, boolean isCircular) {
055        this.throwable = circular;
056        this.className = circular.getClass().getName();
057        this.message = circular.getMessage();
058        this.stackTraceElementProxyArray = EMPTY_STEP;
059        this.circular = true;
060    }
061
062    public ThrowableProxy(Throwable throwable, Set<Throwable> alreadyProcessedSet) {
063
064        this.throwable = throwable;
065        this.className = throwable.getClass().getName();
066        this.message = throwable.getMessage();
067        this.stackTraceElementProxyArray = ThrowableProxyUtil.steArrayToStepArray(throwable.getStackTrace());
068        this.circular = false;
069
070        alreadyProcessedSet.add(throwable);
071
072        Throwable nested = throwable.getCause();
073        if (nested != null) {
074            if (alreadyProcessedSet.contains(nested)) {
075                this.cause = new ThrowableProxy(nested, true);
076            } else {
077                this.cause = new ThrowableProxy(nested, alreadyProcessedSet);
078                this.cause.commonFrames = ThrowableProxyUtil.findNumberOfCommonFrames(nested.getStackTrace(),
079                        stackTraceElementProxyArray);
080            }
081        }
082
083        Throwable[] throwableSuppressed = throwable.getSuppressed();
084        // while JDK's implementation of getSuppressed() will always return a non-null array,
085        // this might not be the case in mocked throwables. We are being extra defensive here.
086        if (OptionHelper.isNotEmtpy(throwableSuppressed)) {
087            List<ThrowableProxy> suppressedList = new ArrayList<>(throwableSuppressed.length);
088            for (Throwable sup : throwableSuppressed) {
089                if (alreadyProcessedSet.contains(sup)) {
090                    ThrowableProxy throwableProxy = new ThrowableProxy(sup, true);
091                    suppressedList.add(throwableProxy);
092                } else {
093                    ThrowableProxy throwableProxy = new ThrowableProxy(sup, alreadyProcessedSet);
094                    throwableProxy.commonFrames = ThrowableProxyUtil.findNumberOfCommonFrames(sup.getStackTrace(),
095                            stackTraceElementProxyArray);
096                    suppressedList.add(throwableProxy);
097                }
098            }
099            this.suppressed = suppressedList.toArray(new ThrowableProxy[suppressedList.size()]);
100        }
101    }
102
103    public Throwable getThrowable() {
104        return throwable;
105    }
106
107    public String getMessage() {
108        return message;
109    }
110
111    /*
112     * (non-Javadoc)
113     * 
114     * @see ch.qos.logback.classic.spi.IThrowableProxy#getClassName()
115     */
116    public String getClassName() {
117        return className;
118    }
119
120    public StackTraceElementProxy[] getStackTraceElementProxyArray() {
121        return stackTraceElementProxyArray;
122    }
123
124    @Override
125    public boolean isCyclic() {
126        return circular;
127    }
128
129    public int getCommonFrames() {
130        return commonFrames;
131    }
132
133    /*
134     * (non-Javadoc)
135     * 
136     * @see ch.qos.logback.classic.spi.IThrowableProxy#getCause()
137     */
138    public IThrowableProxy getCause() {
139        return cause;
140    }
141
142    public IThrowableProxy[] getSuppressed() {
143        return suppressed;
144    }
145
146    public PackagingDataCalculator getPackagingDataCalculator() {
147        // if original instance (non-deserialized), and packagingDataCalculator
148        // is not already initialized, then create an instance.
149        // here we assume that (throwable == null) for deserialized instances
150        if (throwable != null && packagingDataCalculator == null) {
151            packagingDataCalculator = new PackagingDataCalculator();
152        }
153        return packagingDataCalculator;
154    }
155
156    public void calculatePackagingData() {
157        if (calculatedPackageData) {
158            return;
159        }
160        PackagingDataCalculator pdc = this.getPackagingDataCalculator();
161        if (pdc != null) {
162            calculatedPackageData = true;
163            pdc.calculate(this);
164        }
165    }
166
167    public void fullDump() {
168        StringBuilder builder = new StringBuilder();
169        for (StackTraceElementProxy step : stackTraceElementProxyArray) {
170            String string = step.toString();
171            builder.append(CoreConstants.TAB).append(string);
172            ThrowableProxyUtil.subjoinPackagingData(builder, step);
173            builder.append(CoreConstants.LINE_SEPARATOR);
174        }
175        System.out.println(builder.toString());
176    }
177
178}