    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 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}