View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.classic.spi;
15  
16  import ch.qos.logback.core.CoreConstants;
17  import ch.qos.logback.core.util.OptionHelper;
18  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.IdentityHashMap;
22  import java.util.List;
23  import java.util.Set;
24  
25  public class ThrowableProxy implements IThrowableProxy {
26  
27      static final StackTraceElementProxy[] EMPTY_STEP = new StackTraceElementProxy[0];
28  
29      private Throwable throwable;
30      private String className;
31      private String message;
32      // package-private because of ThrowableProxyUtil
33      StackTraceElementProxy[] stackTraceElementProxyArray;
34      // package-private because of ThrowableProxyUtil
35      int commonFrames;
36      private ThrowableProxy cause;
37      private ThrowableProxy[] suppressed = NO_SUPPRESSED;
38  
39      // private final Set<Throwable> alreadyProcessedSet;
40  
41      private transient PackagingDataCalculator packagingDataCalculator;
42      private boolean calculatedPackageData = false;
43  
44      // the getter is called isCyclic
45      private boolean cyclic;
46  
47      private static final ThrowableProxy[] NO_SUPPRESSED = new ThrowableProxy[0];
48  
49      public ThrowableProxy(Throwable throwable) {
50          // use an identity set to detect cycles in the throwable chain
51          this(throwable, Collections.newSetFromMap(new IdentityHashMap<>()));
52      }
53  
54      // used for circular exceptions
55      private ThrowableProxy(Throwable circular, boolean isCyclic) {
56          this.throwable = circular;
57          this.className = circular.getClass().getName();
58          this.message = circular.getMessage();
59          this.stackTraceElementProxyArray = EMPTY_STEP;
60          this.cyclic = true;
61      }
62  
63      public ThrowableProxy(Throwable throwable, Set<Throwable> alreadyProcessedSet) {
64  
65          this.throwable = throwable;
66          this.className = throwable.getClass().getName();
67          this.message = throwable.getMessage();
68          this.stackTraceElementProxyArray = ThrowableProxyUtil.steArrayToStepArray(throwable.getStackTrace());
69          this.cyclic = false;
70  
71          alreadyProcessedSet.add(throwable);
72  
73          Throwable nested = throwable.getCause();
74          if (nested != null) {
75              if (alreadyProcessedSet.contains(nested)) {
76                  this.cause = new ThrowableProxy(nested, true);
77              } else {
78                  this.cause = new ThrowableProxy(nested, alreadyProcessedSet);
79                  this.cause.commonFrames = ThrowableProxyUtil.findNumberOfCommonFrames(nested.getStackTrace(),
80                          stackTraceElementProxyArray);
81              }
82          }
83  
84          Throwable[] throwableSuppressed = throwable.getSuppressed();
85          // while JDK's implementation of getSuppressed() will always return a non-null array,
86          // this might not be the case in mocked throwables. We are being extra defensive here.
87          if (OptionHelper.isNotEmtpy(throwableSuppressed)) {
88              List<ThrowableProxy> suppressedList = new ArrayList<>(throwableSuppressed.length);
89              for (Throwable sup : throwableSuppressed) {
90                  if (alreadyProcessedSet.contains(sup)) {
91                      ThrowableProxy throwableProxy = new ThrowableProxy(sup, true);
92                      suppressedList.add(throwableProxy);
93                  } else {
94                      ThrowableProxy throwableProxy = new ThrowableProxy(sup, alreadyProcessedSet);
95                      throwableProxy.commonFrames = ThrowableProxyUtil.findNumberOfCommonFrames(sup.getStackTrace(),
96                              stackTraceElementProxyArray);
97                      suppressedList.add(throwableProxy);
98                  }
99              }
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 }