| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more contributor license |
| * agreements. See the NOTICE file distributed with this work for additional information regarding |
| * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the License. You may obtain a |
| * copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software distributed under the License |
| * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
| * or implied. See the License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package org.apache.geode.management.internal.cli.util; |
| |
| /** |
| * Helper class to navigate through the nested causes of an exception. Provides method to |
| * <ul> |
| * <li>find index of an Exception of a specific type |
| * <li>find Root Cause of the Exception |
| * <li>retrieve a nested cause at a specific index/depth |
| * <li>find a cause by specific type |
| * </ul> |
| * |
| * @since GemFire 7.0 |
| */ |
| public class CauseFinder { |
| private static final int START_INDEX = 0; |
| |
| /** |
| * Returns the index of a first nested cause which is of the given Throwable type or its sub-type |
| * depending on <code>isSubtypeOk</code> value. |
| * <p /> |
| * NOTE: It looks for the "nested" causes & doesn't try to match the <code>causeClass</code> of |
| * the <code>parent</code>. |
| * <p /> |
| * |
| * Returns -1 if a cause with the given Throwable type is not found. |
| * |
| * @param parent parent Throwable instance |
| * @param causeClass type of the nested Throwable cause |
| * @param isSubtypeOk whether any matching sub-type is required or exact match is required |
| * @return index/depth of the cause starting from the 'top most' Throwable in the stack. -1 if |
| * can't find it. |
| */ |
| public static int indexOfCause(Throwable parent, Class<? extends Throwable> causeClass, |
| final boolean isSubtypeOk) { |
| return indexOfCause(parent, causeClass, START_INDEX, isSubtypeOk); |
| } |
| |
| /** |
| * Returns the index of a first nested cause which is of the given Throwable type or its sub-type |
| * depending on <code>isSubtypeOk</code> value. |
| * <p /> |
| * NOTE: It looks for the "nested" causes & doesn't try to match the <code>causeClass</code> of |
| * the <code>parent</code>. |
| * <p /> |
| * |
| * Returns -1 if a cause with the given Throwable type is not found. |
| * |
| * @param parent parent Throwable instance |
| * @param causeClass type of the nested Throwable cause |
| * @param cindex variable to store current index/depth of the cause |
| * @param isSubtypeOk whether any matching sub-type is required or exact match is required |
| * @return index/depth of the cause starting from the 'top most' Throwable in the stack. -1 if |
| * can't find it. |
| */ |
| private static int indexOfCause(Throwable parent, Class<? extends Throwable> causeClass, |
| final int cindex, final boolean isSubtypeOk) { |
| int resultIndex = cindex; |
| Throwable cause = parent.getCause(); |
| |
| // (cause is not null & cause is not of type causeClass) |
| if (cause != null && !isMatching(cause, causeClass, isSubtypeOk)) { |
| // recurse deeper |
| resultIndex = indexOfCause(cause, causeClass, cindex + 1, isSubtypeOk); |
| } else if (cause != null && isMatching(cause, causeClass, isSubtypeOk)) { |
| resultIndex = cindex != -1 ? cindex + 1 : cindex; |
| } else if (cause == null) { |
| resultIndex = -1; |
| } |
| |
| return resultIndex; |
| } |
| |
| /** |
| * Returns whether the given <code>cause</code> is assignable or has same type as that of |
| * <code>causeClass</code> depending on <code>isSubtypeOk</code> value. |
| * |
| * @param cause parent Throwable instance |
| * @param causeClass type of the nested Throwable cause |
| * @param isSubtypeOk whether any matching sub-type is required or exact match is required |
| * @return true if <code>cause</code> is assignable or has same type as that of |
| * <code>causeClass</code>. false otherwise |
| */ |
| // Note: No not null check on 'Throwable cause' - currently called after null check |
| private static boolean isMatching(Throwable cause, Class<? extends Throwable> causeClass, |
| final boolean isSubtypeOk) { |
| return isSubtypeOk ? causeClass.isInstance(cause) : causeClass == cause.getClass(); |
| } |
| |
| /** |
| * Returns nested 'root' cause of the given parent Throwable. Will return the same Throwable if it |
| * has no 'cause'. |
| * |
| * @param parent Throwable whose root cause is to be found |
| * @return nested root cause. |
| * @throws IllegalArgumentException when parent is <code>null</code> |
| */ |
| public static Throwable getRootCause(Throwable parent) { |
| return getRootCause(parent, 0); |
| } |
| |
| /** |
| * Returns nested 'root' cause of the given parent Throwable. Will return the same Throwable if it |
| * has no 'cause'. If <code>depth</code> is 0, does a <code>null</code> check on the given |
| * <code>parent Throwable</code> & if <code>parent</code> is <code>null</code>, throws |
| * {@link IllegalArgumentException}. |
| * |
| * @param parent Throwable whose root cause is to be found |
| * @param depth recurse depth indicator |
| * @return nested root cause. |
| * @throws IllegalArgumentException when parent is <code>null</code> at <code>depth = 0</code> |
| */ |
| // Note: private method |
| private static Throwable getRootCause(Throwable parent, int depth) { |
| if (depth == 0) { |
| if (parent == null) { |
| throw new IllegalArgumentException("Given parent Throwable is null."); |
| } |
| } |
| Throwable theCause = parent.getCause(); |
| if (theCause != null) { |
| // recurse deeper |
| return getRootCause(theCause, depth + 1); |
| } |
| |
| return parent; |
| } |
| |
| /** |
| * Returns cause at the specified depth/index starting from the 'top most' Throwable |
| * <code>parent</code> in the stack. Returns <code>null</code> if it can't find it. |
| * |
| * @param parent Throwable to use to find the cause at given index/depth |
| * @param requiredIndex index/depth of nesting the cause |
| * @return cause at the specified index starting from the 'top most' Throwable in the stack. |
| * <code>null</code> if it can't find it. |
| */ |
| public static Throwable causeAt(Throwable parent, final int requiredIndex) { |
| // |
| // TODO: |
| // NEED TO CHECK WHETHER RIGHT PARENT CAUSE RETRUNED |
| // |
| if (parent != null && requiredIndex > 0) { |
| // recurse deeper |
| return causeAt(parent.getCause(), requiredIndex - 1); |
| } |
| |
| // when there isn't required depth |
| if (requiredIndex > 0) { |
| return null; |
| } |
| |
| return parent; |
| } |
| |
| /** |
| * Returns the first occurrence of nested cause of <code>parent</code> which matches the specified |
| * <code>causeType</code> or its sub-type depending on <code>isSubtypeOk</code> value. |
| * |
| * @param parent Throwable to use to find the cause of given type |
| * @param causeType type of the nested Throwable cause |
| * @param isSubtypeOk whether any matching sub-type is required or exact match is required |
| * @return the first occurrence of nested cause which matches the specified <code>causeType</code> |
| * or its sub-type. <code>null</code> if it can't find it. |
| */ |
| public static Throwable causeByType(Throwable parent, Class<? extends Throwable> causeType, |
| boolean isSubtypeOk) { |
| Throwable cause = null; |
| int foundAtIndex = indexOfCause(parent, causeType, isSubtypeOk); |
| if (foundAtIndex != -1) { |
| cause = causeAt(parent, foundAtIndex); |
| } |
| return cause; |
| } |
| } |