001// Copyright 2006, 2007, 2008, 2009, 2010 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry5.ioc.internal.services; 016 017import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 018import org.apache.tapestry5.ioc.services.*; 019 020import java.util.*; 021 022public class ExceptionAnalyzerImpl implements ExceptionAnalyzer 023{ 024 private final PropertyAccess propertyAccess; 025 026 private final Set<String> throwableProperties; 027 028 /** 029 * A tuple used to communicate up a lavel both the exception info 030 * and the next exception in the stack. 031 */ 032 private static class ExceptionData 033 { 034 final ExceptionInfo exceptionInfo; 035 final Throwable cause; 036 037 public ExceptionData(ExceptionInfo exceptionInfo, Throwable cause) 038 { 039 this.exceptionInfo = exceptionInfo; 040 this.cause = cause; 041 } 042 } 043 044 public ExceptionAnalyzerImpl(PropertyAccess propertyAccess) 045 { 046 this.propertyAccess = propertyAccess; 047 048 throwableProperties = CollectionFactory.newSet(this.propertyAccess.getAdapter(Throwable.class) 049 .getPropertyNames()); 050 } 051 052 @Override 053 public ExceptionAnalysis analyze(Throwable rootException) 054 { 055 List<ExceptionInfo> list = CollectionFactory.newList(); 056 057 Throwable t = rootException; 058 059 ExceptionInfo previousInfo = null; 060 061 while (t != null) 062 { 063 ExceptionData data = extractData(t); 064 065 ExceptionInfo info = data.exceptionInfo; 066 067 if (addsValue(previousInfo, info)) 068 { 069 list.add(info); 070 previousInfo = info; 071 } 072 073 t = data.cause; 074 } 075 076 return new ExceptionAnalysisImpl(list); 077 } 078 079 /** 080 * We want to filter out exceptions that do not provide any additional value. Additional value includes: an 081 * exception message not present in the containing exception or a property value not present in the containing 082 * exception. Also the first exception is always valued and the last exception (with the stack trace) is valued. 083 * 084 * @param previousInfo 085 * @param info 086 * @return 087 */ 088 private boolean addsValue(ExceptionInfo previousInfo, ExceptionInfo info) 089 { 090 if (previousInfo == null) 091 return true; 092 093 if (!info.getStackTrace().isEmpty()) 094 return true; 095 096 // TAP5-508: This adds back in a large number of frames that used to be squashed. 097 if (!info.getClassName().equals(previousInfo.getClassName())) 098 return true; 099 100 if (!previousInfo.getMessage().contains(info.getMessage())) 101 return true; 102 103 for (String name : info.getPropertyNames()) 104 { 105 if (info.getProperty(name).equals(previousInfo.getProperty(name))) 106 continue; 107 108 // Found something new and different at this level. 109 110 return true; 111 } 112 113 // This exception adds nothing that is not present at a higher level. 114 115 return false; 116 } 117 118 private ExceptionData extractData(Throwable t) 119 { 120 Map<String, Object> properties = CollectionFactory.newMap(); 121 122 ClassPropertyAdapter adapter = propertyAccess.getAdapter(t); 123 124 Throwable cause = null; 125 126 for (String name : adapter.getPropertyNames()) 127 { 128 PropertyAdapter pa = adapter.getPropertyAdapter(name); 129 130 if (!pa.isRead()) 131 continue; 132 133 if (cause == null && Throwable.class.isAssignableFrom(pa.getType())) 134 { 135 // Ignore the property, but track it as the cause. 136 137 Throwable nestedException = (Throwable) pa.get(t); 138 139 // Handle the case where an exception is its own cause (avoid endless loop!) 140 if (t != nestedException) 141 cause = nestedException; 142 143 continue; 144 } 145 146 // Otherwise, ignore properties defined by the Throwable class 147 148 if (throwableProperties.contains(name)) 149 continue; 150 151 Object value = pa.get(t); 152 153 if (value == null) 154 continue; 155 156 // An interesting property, let's save it for the analysis. 157 158 properties.put(name, value); 159 } 160 161 // Provide the stack trace only at the deepest exception. 162 163 List<StackTraceElement> stackTrace = Collections.emptyList(); 164 165 // Usually, I'd use a terniary expression here, but Generics gets in 166 // the way here. 167 168 if (cause == null) 169 stackTrace = Arrays.asList(t.getStackTrace()); 170 171 ExceptionInfo info = new ExceptionInfoImpl(t, properties, stackTrace); 172 173 return new ExceptionData(info, cause); 174 } 175}