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}