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}