3

We have a BaseException extends Exception in our project, and basically all our other exceptions derive from this class. I want to change some methods that deal with the "cause stack" at runtime.

As starting point, I wrote the following method:

class BaseException extends Exception {
...

  /**
   * Helper: creates a list containing the complete "cause stack" of this exception.
   * Please note: the exception on which this method is called is part of result!
   *
   * @return a {@link List} of all "causes" of this exception 
   */
  List<Throwable> getAllCauses() {
    Throwable cause = this;
    List<Throwable> causes = new ArrayList<>();
    while (cause != null) {
      causes.add(cause);
      cause = cause.getCause();
    }          
    return causes;
  }

This gets the job done, although it is not perfect (name isn't exactly great, and single layer of abstraction is violated, too) .

But still: is there a "more elegant" way of collecting this result? Especially given the fact that it would helpful to directly return a Stream<Throwable>.

( I am mainly wondering if there is a java8 lambda/idiom that could help here )

GhostCat
  • 137,827
  • 25
  • 176
  • 248

3 Answers3

11

This article should be of help. In particular,

Stream<Throwable> causes(Throwable t){
    if (t == null) return Stream.empty();
    return Stream.concat(Stream.of(t), causes(t.getCause()));
}
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • 2
    Just a tiny caveat: This is susceptible to cycles in the cause chain (but so is your current implementation). I don't think this will be an issue, but e.g. Apache's [`ExceptionUtils.getThrowables`](https://commons.apache.org/proper/commons-lang/javadocs/api-3.6/org/apache/commons/lang3/exception/ExceptionUtils.html#getThrowables-java.lang.Throwable-) explicitly mentions it. Otherwise this is pret-ty nifty. – Marvin Aug 23 '17 at 19:36
  • 1
    It's also very garbage-intensive and may not optimize as well as plain loop. –  Aug 23 '17 at 19:46
  • first upvote. there is an implementation I implemented [here](https://stackoverflow.com/questions/44700006/is-there-java-stream-equivalent-to-while-with-variable-assignment/44700862#44700862) by spliterator. I wish it helped. – holi-java Aug 24 '17 at 04:41
  • 1
    @Arkadiy I don't care about cycles - our existing code doesn't either. I agree about the garbage - but well: we are talking about exceptions here. Exceptions shouldnt be thrown thousands of time per second. And I guess creating the stack trace and stacking things up will outweigh the cost of such calls anyway. – GhostCat Aug 24 '17 at 06:50
4

Here is my implementation that implemented by Spliterator as below:

public static <T> Stream<T> 
       iterateUntil(T seed, UnaryOperator<T> generator, Predicate<T> proceed){
  return stream(new AbstractSpliterator<T>(Long.MAX_VALUE, Spliterator.ORDERED){
    private T value = seed;
    public boolean tryAdvance(Consumer<? super T> action){
      if(!proceed.test(value)) return false;
      action.accept(value);
      value = generator.apply(value);
      return true;
    }
  }, false);
}

Then you can implements your getCauses as below and it drop the recursive calls:

List<Throwable> getAllCauses() {
    return iterateUntil(this, Throwable::getCause, Objects::nonNull)
              .collect(toList());
}
holi-java
  • 29,655
  • 7
  • 72
  • 83
  • @GhostCat thanks, sir. in java8 the almost way you can operate a Spliterator as possible to achieve your way. there many answer I wrote base on it. e.g: `generateUntil` , `chunk` and .etc. in this way you can traversing `getCause` until the terminal operation is called. – holi-java Aug 24 '17 at 07:39
1

Using some enumeration seems more appropriate to me, then something like

class BaseException extends Exception {
...

  Enumeration<Throwable> getCauses() {
    return new Enumeration<Throwable>() {
       private Throwable current = BaseException.this;
       public boolean hasMoreElements() {
         return current != null;
       }
       public Throwable nextElement() {
         Throwable c = current;
         current = current.getCause();
         return c;
       }
    }
  }

With Java 8 you can also create a new interface with a default method doing the trick and then use that interface in any of your exception class (slightly better than subclassing Exception?).

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
  • I think I prefer the current solution (as I need a stream, and the enumeration would need to be converted to a stream then). – GhostCat Aug 24 '17 at 07:09