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      private boolean circular;
45  
46      private static final ThrowableProxy[] NO_SUPPRESSED = new ThrowableProxy[0];
47  
48      public ThrowableProxy(Throwable throwable) {
49          // use an identity set to detect cycles in the throwable chain
50          this(throwable, Collections.newSetFromMap(new IdentityHashMap<>()));
51      }
52  
53      // used for circular exceptions
54      private ThrowableProxy(Throwable circular, boolean isCircular) {
55          this.throwable = circular;
56          this.className = circular.getClass().getName();
57          this.message = circular.getMessage();
58          this.stackTraceElementProxyArray = EMPTY_STEP;
59          this.circular = true;
60      }
61  
62      public ThrowableProxy(Throwable throwable, Set<Throwable> alreadyProcessedSet) {
63  
64          this.throwable = throwable;
65          this.className = throwable.getClass().getName();
66          this.message = throwable.getMessage();
67          this.stackTraceElementProxyArray = ThrowableProxyUtil.steArrayToStepArray(throwable.getStackTrace());
68          this.circular = false;
69  
70          alreadyProcessedSet.add(throwable);
71  
72          Throwable nested = throwable.getCause();
73          if (nested != null) {
74              if (alreadyProcessedSet.contains(nested)) {
75                  this.cause = new ThrowableProxy(nested, true);
76              } else {
77                  this.cause = new ThrowableProxy(nested, alreadyProcessedSet);
78                  this.cause.commonFrames = ThrowableProxyUtil.findNumberOfCommonFrames(nested.getStackTrace(),
79                          stackTraceElementProxyArray);
80              }
81          }
82  
83          Throwable[] throwableSuppressed = throwable.getSuppressed();
84          // while JDK's implementation of getSuppressed() will always return a non-null array,
85          // this might not be the case in mocked throwables. We are being extra defensive here.
86          if (OptionHelper.isNotEmtpy(throwableSuppressed)) {
87              List<ThrowableProxy> suppressedList = new ArrayList<>(throwableSuppressed.length);
88              for (Throwable sup : throwableSuppressed) {
89                  if (alreadyProcessedSet.contains(sup)) {
90                      ThrowableProxy throwableProxy = new ThrowableProxy(sup, true);
91                      suppressedList.add(throwableProxy);
92                  } else {
93                      ThrowableProxy throwableProxy = new ThrowableProxy(sup, alreadyProcessedSet);
94                      throwableProxy.commonFrames = ThrowableProxyUtil.findNumberOfCommonFrames(sup.getStackTrace(),
95                              stackTraceElementProxyArray);
96                      suppressedList.add(throwableProxy);
97                  }
98              }
99              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 }