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 overridingMessage;
032    private String message;
033    // package-private because of ThrowableProxyUtil
034    StackTraceElementProxy[] stackTraceElementProxyArray;
035    // package-private because of ThrowableProxyUtil
036    int commonFrames;
037    private ThrowableProxy cause;
038    private ThrowableProxy[] suppressed = NO_SUPPRESSED;
039
040    // private final Set<Throwable> alreadyProcessedSet;
041
042    private transient PackagingDataCalculator packagingDataCalculator;
043    private boolean calculatedPackageData = false;
044
045    // the getter is called isCyclic
046    private boolean cyclic;
047
048    private static final ThrowableProxy[] NO_SUPPRESSED = new ThrowableProxy[0];
049
050    public ThrowableProxy(Throwable throwable) {
051        // use an identity set to detect cycles in the throwable chain
052        this(throwable, Collections.newSetFromMap(new IdentityHashMap<>()));
053    }
054
055    // used for circular exceptions
056    private ThrowableProxy(Throwable circular, boolean isCyclic) {
057        this.throwable = circular;
058        this.className = circular.getClass().getName();
059        this.message = circular.getMessage();
060        this.stackTraceElementProxyArray = EMPTY_STEP;
061        this.cyclic = true;
062    }
063
064    public ThrowableProxy(Throwable throwable, Set<Throwable> alreadyProcessedSet) {
065
066        this.throwable = throwable;
067        this.className = throwable.getClass().getName();
068        this.message = throwable.getMessage();
069        this.overridingMessage = buildOverridingMessage(throwable);
070        this.stackTraceElementProxyArray = ThrowableProxyUtil.steArrayToStepArray(throwable.getStackTrace());
071        this.cyclic = false;
072
073        alreadyProcessedSet.add(throwable);
074
075        Throwable nested = throwable.getCause();
076        if (nested != null) {
077            if (alreadyProcessedSet.contains(nested)) {
078                this.cause = new ThrowableProxy(nested, true);
079            } else {
080                this.cause = new ThrowableProxy(nested, alreadyProcessedSet);
081                this.cause.commonFrames = ThrowableProxyUtil.findNumberOfCommonFrames(nested.getStackTrace(),
082                        stackTraceElementProxyArray);
083            }
084        }
085
086        Throwable[] throwableSuppressed = throwable.getSuppressed();
087        // while JDK's implementation of getSuppressed() will always return a non-null array,
088        // this might not be the case in mocked throwables. We are being extra defensive here.
089        if (OptionHelper.isNotEmtpy(throwableSuppressed)) {
090            List<ThrowableProxy> suppressedList = new ArrayList<>(throwableSuppressed.length);
091            for (Throwable sup : throwableSuppressed) {
092                if (alreadyProcessedSet.contains(sup)) {
093                    ThrowableProxy throwableProxy = new ThrowableProxy(sup, true);
094                    suppressedList.add(throwableProxy);
095                } else {
096                    ThrowableProxy throwableProxy = new ThrowableProxy(sup, alreadyProcessedSet);
097                    throwableProxy.commonFrames = ThrowableProxyUtil.findNumberOfCommonFrames(sup.getStackTrace(),
098                            stackTraceElementProxyArray);
099                    suppressedList.add(throwableProxy);
100                }
101            }
102            this.suppressed = suppressedList.toArray(new ThrowableProxy[suppressedList.size()]);
103        }
104    }
105
106    private String buildOverridingMessage(Throwable throwable) {
107        StringBuilder sb = new StringBuilder();
108        ThrowableProxyUtil.appendNominalFirstLine(sb, throwable.getClass().getName(), throwable.getMessage());
109        String messageFromToString = throwable.toString();
110        String nominalMessage = sb.toString();
111        if (!nominalMessage.equals(messageFromToString)) {
112            return messageFromToString;
113        } else {
114            return null;
115        }
116    }
117
118    public Throwable getThrowable() {
119        return throwable;
120    }
121
122    public String getMessage() {
123        return message;
124    }
125
126    /*
127     * (non-Javadoc)
128     *
129     * @see ch.qos.logback.classic.spi.IThrowableProxy#getOverridingMessage()
130     */
131    @Override
132    public String getOverridingMessage() {
133        return overridingMessage;
134    }
135
136    /*
137     * (non-Javadoc)
138     * 
139     * @see ch.qos.logback.classic.spi.IThrowableProxy#getClassName()
140     */
141    public String getClassName() {
142        return className;
143    }
144
145    public StackTraceElementProxy[] getStackTraceElementProxyArray() {
146        return stackTraceElementProxyArray;
147    }
148
149    @Override
150    public boolean isCyclic() {
151        return cyclic;
152    }
153
154    public int getCommonFrames() {
155        return commonFrames;
156    }
157
158    /*
159     * (non-Javadoc)
160     * 
161     * @see ch.qos.logback.classic.spi.IThrowableProxy#getCause()
162     */
163    public IThrowableProxy getCause() {
164        return cause;
165    }
166
167    public IThrowableProxy[] getSuppressed() {
168        return suppressed;
169    }
170
171    public PackagingDataCalculator getPackagingDataCalculator() {
172        // if original instance (non-deserialized), and packagingDataCalculator
173        // is not already initialized, then create an instance.
174        // here we assume that (throwable == null) for deserialized instances
175        if (throwable != null && packagingDataCalculator == null) {
176            packagingDataCalculator = new PackagingDataCalculator();
177        }
178        return packagingDataCalculator;
179    }
180
181    public void calculatePackagingData() {
182        if (calculatedPackageData) {
183            return;
184        }
185        PackagingDataCalculator pdc = this.getPackagingDataCalculator();
186        if (pdc != null) {
187            calculatedPackageData = true;
188            pdc.calculate(this);
189        }
190    }
191
192    public void fullDump() {
193        StringBuilder builder = new StringBuilder();
194        for (StackTraceElementProxy step : stackTraceElementProxyArray) {
195            String string = step.toString();
196            builder.append(CoreConstants.TAB).append(string);
197            ThrowableProxyUtil.subjoinPackagingData(builder, step);
198            builder.append(CoreConstants.LINE_SEPARATOR);
199        }
200        System.out.println(builder.toString());
201    }
202
203}