    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    }