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