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
015 package org.apache.tapestry5.ioc.internal.services;
016
017 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
018 import org.apache.tapestry5.ioc.services.*;
019
020 import java.util.*;
021
022 public 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 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 public ExceptionAnalysis analyze(Throwable rootException)
053 {
054 List<ExceptionInfo> list = CollectionFactory.newList();
055
056 Throwable t = rootException;
057
058 ExceptionInfo previousInfo = null;
059
060 while (t != null)
061 {
062 ExceptionData data = extractData(t);
063
064 ExceptionInfo info = data.exceptionInfo;
065
066 if (addsValue(previousInfo, info))
067 {
068 list.add(info);
069 previousInfo = info;
070 }
071
072 t = data.cause;
073 }
074
075 return new ExceptionAnalysisImpl(list);
076 }
077
078 /**
079 * We want to filter out exceptions that do not provide any additional value. Additional value includes: an
080 * exception message not present in the containing exception or a property value not present in the containing
081 * exception. Also the first exception is always valued and the last exception (with the stack trace) is valued.
082 *
083 * @param previousInfo
084 * @param info
085 * @return
086 */
087 private boolean addsValue(ExceptionInfo previousInfo, ExceptionInfo info)
088 {
089 if (previousInfo == null)
090 return true;
091
092 if (!info.getStackTrace().isEmpty())
093 return true;
094
095 // TAP5-508: This adds back in a large number of frames that used to be squashed.
096 if (!info.getClassName().equals(previousInfo.getClassName()))
097 return true;
098
099 if (!previousInfo.getMessage().contains(info.getMessage()))
100 return true;
101
102 for (String name : info.getPropertyNames())
103 {
104 if (info.getProperty(name).equals(previousInfo.getProperty(name)))
105 continue;
106
107 // Found something new and different at this level.
108
109 return true;
110 }
111
112 // This exception adds nothing that is not present at a higher level.
113
114 return false;
115 }
116
117 private ExceptionData extractData(Throwable t)
118 {
119 Map<String, Object> properties = CollectionFactory.newMap();
120
121 ClassPropertyAdapter adapter = propertyAccess.getAdapter(t);
122
123 Throwable cause = null;
124
125 for (String name : adapter.getPropertyNames())
126 {
127 PropertyAdapter pa = adapter.getPropertyAdapter(name);
128
129 if (!pa.isRead())
130 continue;
131
132 if (cause == null && Throwable.class.isAssignableFrom(pa.getType()))
133 {
134 // Ignore the property, but track it as the cause.
135
136 Throwable nestedException = (Throwable) pa.get(t);
137
138 // Handle the case where an exception is its own cause (avoid endless loop!)
139 if (t != nestedException)
140 cause = nestedException;
141
142 continue;
143 }
144
145 // Otherwise, ignore properties defined by the Throwable class
146
147 if (throwableProperties.contains(name))
148 continue;
149
150 Object value = pa.get(t);
151
152 if (value == null)
153 continue;
154
155 // An interesting property, let's save it for the analysis.
156
157 properties.put(name, value);
158 }
159
160 // Provide the stack trace only at the deepest exception.
161
162 List<StackTraceElement> stackTrace = Collections.emptyList();
163
164 // Usually, I'd use a terniary expression here, but Generics gets in
165 // the way here.
166
167 if (cause == null)
168 stackTrace = Arrays.asList(t.getStackTrace());
169
170 ExceptionInfo info = new ExceptionInfoImpl(t, properties, stackTrace);
171
172 return new ExceptionData(info, cause);
173 }
174 }