Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

rethrow java exception with new message, preserving the exception type if it is in the method declaration list

I am trying to create a helper method that will eliminate the need of having code like this:

void foo() throws ExceptionA, ExceptionB, DefaultException {
  try {
     doSomething(); // that throws ExceptionA, ExceptionB or others
  } catch (Exception e) {
    if (e instanceof ExceptionA)
        throw new ExceptionA("extra message", e);
    if (e instanceof ExceptionB)
        throw new ExceptionB("extra message", e);

    throw new DefaultException("extra message", e);
  }
}

The problem is that I need to maintain the throws list in the function declaration and in the body of the function at the same time. I am looking how to avoid that and to make changing the throws list sufficient and my code to looks like:

void foo() throws ExceptionA, ExceptionB, DefaultException {
  try {
     doSomething(); // that throws ExceptionA, ExceptionB or others
  } catch (Exception e) {
    rethrow(DefaultException.class, "extra message", e);
  }
}

Where rethrow method will be smart enough to recognize the throws list from the method declaration.

This way when I change the list of type that my method propagates in the throws list I to not need to change the body.

The following is a function that could solve the problem. The problem is because it does not know what type of exception it will throw its throws declaration has to say Exception, but if it does this, the method that is going to use it will need to specify it as well, and the whole idea of using the throws list goes to hell.

Any suggestions how this could be solved?

@SuppressWarnings("unchecked")
public static void rethrow(Class<?> defaultException, String message, Exception e) throws Exception
{
  final StackTraceElement[] ste = Thread.currentThread().getStackTrace();

  final StackTraceElement element = ste[ste.length - 1 - 1];

  Method method = null;

  try {
     method = getMethod(element);
  } catch (ClassNotFoundException ignore) {
     // ignore the Class not found exception - just make sure the method is null
     method = null;
  }

  boolean preserveType = true;

  if (method != null) {

     // if we obtained the method successfully - preserve the type 
     // only if it is in the list of the thrown exceptions
     preserveType = false;

     final Class<?> exceptions[] = method.getExceptionTypes();

     for (Class<?> cls : exceptions) {
        if (cls.isInstance(e)) {
           preserveType = true;
           break;
        }
     }
  }

  if (preserveType)
  {
     // it is throws exception - preserve the type
     Constructor<Exception> constructor;
     Exception newEx = null;
     try {
        constructor = ((Constructor<Exception>) e.getClass().getConstructor());
        newEx = constructor.newInstance(message, e);
     } catch (Exception ignore) {
        // ignore this exception we prefer to throw the original
        newEx = null;
     }

     if (newEx != null)
        throw newEx;
  }

  // if we get here this means we do not want, or we cannot preserve the type
  // just rethrow it with the default type

  Constructor<Exception> constructor;
  Exception newEx = null;

  if (defaultException != null) {
     try {
        constructor = (Constructor<Exception>) defaultException.getConstructor();
        newEx = constructor.newInstance(message, e);
     } catch (Exception ignore) {
        // ignore this exception we prefer to throw the original
        newEx = null;
     }

     if (newEx != null)
        throw newEx;
  }

  // if we get here we were unable to construct the default exception
  // there lets log the message that we are going to lose and rethrow
  // the original exception

  log.warn("this message was not propagated as part of the exception: \"" + message + "\"");
  throw e;
}

Update 1: I can use RuntimeException to avoid the need of throws declaration, but in this case I am losing the type of the exception which is one of the most important points.

Ideas how I can resolve this?

like image 675
gsf Avatar asked Jul 10 '14 23:07

gsf


1 Answers

I'm guessing that code where you're doing real work (ie. the part where you're not tinkering with exceptions) looks like this.

public void doSomeWork( ... ) throws ExceptionA, ExceptionB, DefaultException
{
    try
    {
        // some code that could throw ExceptionA
        ...
        // some code that could throw OtherExceptionA
        ...
        // some code that could throw ExceptionB
        ...
        // some code that could throw OtherExceptionB
    }
    catch (Exception e) 
    {
        if( e instanceof ExceptionA )
        {
            throw new ExceptionA("extra message", e);
        }
        if( e instanceof ExceptionB )
        {
            throw new ExceptionB("extra message", e);
        }

        throw new DefaultException("extra message", e);
     }
}

There are two better approaches

First Approach

public void doSomeWork( ... ) throws ExceptionA, ExceptionB, DefaultException
{
    // some code that could throw ExceptionA
    ...
    try
    {
        // some code that could throw OtherExceptionA
        ...
    }
    catch (Exception e) 
    {
        throw new DefaultException("extra message", e);
    }
    // some code that could throw ExceptionB
    ...
    try
    {
        // some code that could throw OtherExceptionB
    }
    catch (Exception e) 
    {
        throw new DefaultException("extra message", e);
    }
}

Second Approach

public void doSomeWork( ... ) throws ExceptionA, ExceptionB, DefaultException
{
    try
    {
        // some code that could throw ExceptionA
        ...
        // some code that could throw OtherExceptionA
        ...
        // some code that could throw ExceptionB
        ...
        // some code that could throw OtherExceptionB
    }
    catch (OtherExceptionA | OtherExceptionB e) 
    {
        throw new DefaultException("extra message", e);
    }
}

The first approach is good if you want to continue execution at all costs and catch and wrap RuntimeExceptions if you run into them. Generally you don't want to do this, and it's better to let them propagate up, as you probably can't handle them.

The second approach is generally the best. Here you're explicitly pointing out which exceptions you can handle, and dealing with them by wrapping them. Unexpected RuntimeExceptions propagate up, as they should unless you have some way of dealing with them.

Just a general comment: playing with StackTraceElements isn't considered to be a great idea. You may end up getting an empty array from Thread.currentThread().getStackTrace() (although you most likely will not if using a modern Oracle JVM), and the depth of the calling method isn't always length-2, it may be length-1 particularly in older versions of the Oracle JVM.

You can read more about this problem in this question.

like image 137
jr. Avatar answered Oct 07 '22 13:10

jr.