    001// Copyright 2008-2013, 2023 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.commons.util;
    016
    017import java.lang.annotation.Annotation;
    018
    019import org.apache.tapestry5.commons.services.ClassPropertyAdapter;
    020import org.apache.tapestry5.commons.services.PropertyAccess;
    021
    022/**
    023 * Contains static methods useful for manipulating exceptions.
    024 */
    025public class ExceptionUtils
    026{
    027    /**
    028     * Locates a particular type of exception, working its way via the cause property of each exception in the exception
    029     * stack.
    030     *
    031     * @param t    the outermost exception
    032     * @param type the type of exception to search for
    033     * @return the first exception of the given type, if found, or null
    034     */
    035    public static <T extends Throwable> T findCause(Throwable t, Class<T> type)
    036    {
    037        Throwable current = t;
    038
    039        while (current != null)
    040        {
    041            if (type.isInstance(current))
    042            {
    043                return type.cast(current);
    044            }
    045
    046            // Not a match, work down.
    047
    048            current = current.getCause();
    049        }
    050
    051        return null;
    052    }
    053
    054    /**
    055     * Locates a particular type of exception, working its way down via any property that returns some type of Exception.
    056     * This is more expensive, but more accurate, than {@link #findCause(Throwable, Class)} as it works with older exceptions
    057     * that do not properly implement the (relatively new) {@linkplain Throwable#getCause() cause property}.
    058     *
    059     * @param t      the outermost exception
    060     * @param type   the type of exception to search for
    061     * @param access used to access properties
    062     * @return the first exception of the given type, if found, or null
    063     */
    064    public static <T extends Throwable> T findCause(Throwable t, Class<T> type, PropertyAccess access)
    065    {
    066        Throwable current = t;
    067
    068        while (current != null)
    069        {
    070            if (type.isInstance(current))
    071            {
    072                return type.cast(current);
    073            }
    074
    075            Throwable next = null;
    076
    077            ClassPropertyAdapter adapter = access.getAdapter(current);
    078
    079            for (String name : adapter.getPropertyNames())
    080            {
    081
    082                Object value = adapter.getPropertyAdapter(name).get(current);
    083
    084                if (value != null && value != current && value instanceof Throwable)
    085                {
    086                    next = (Throwable) value;
    087                    break;
    088                }
    089            }
    090
    091            current = next;
    092        }
    093
    094
    095        return null;
    096    }
    097
    098    /**
    099     * Extracts the message from an exception. If the exception's message is null, returns the exceptions class name.
    100     *
    101     * @param exception
    102     *         to extract message from
    103     * @return message or class name
    104     * @since 5.4
    105     */
    106    public static String toMessage(Throwable exception)
    107    {
    108        assert exception != null;
    109
    110        String message = exception.getMessage();
    111
    112        if (message != null)
    113            return message;
    114
    115        return exception.getClass().getName();
    116    }
    117
    118    /**
    119     * Tells whether an exception annotated with a given annotation is found in the stack
    120     * trace.
    121     * @return <code>true</code> or <code>false</code>
    122     * @since 5.8.3
    123     */
    124    public static boolean isAnnotationInStackTrace(Throwable t, Class<? extends Annotation> annotationClass)
    125    {
    126        boolean answer = false;
    127        Throwable current = t;
    128
    129        while (current != null)
    130        {
    131            if (current.getClass().isAnnotationPresent(annotationClass))
    132            {
    133                answer = true;
    134                break;
    135            }
    136
    137            // Not a match, work down.
    138
    139            current = current.getCause();
    140        }
    141
    142        return answer;
    143    }
    144}