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}