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}