rx.exceptions.Exceptions Maven / Gradle / Ivy
The newest version!
/**
* Copyright 2014 Netflix, Inc.
*
* Licensed 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 rx.exceptions;
import java.util.*;
import rx.Observer;
import rx.SingleSubscriber;
/**
* Utility class with methods to wrap checked exceptions and
* manage fatal and regular exception delivery.
*/
public final class Exceptions {
private static final int MAX_DEPTH = 25;
/** Utility class, no instances. */
private Exceptions() {
throw new IllegalStateException("No instances!");
}
/**
* Convenience method to throw a {@code RuntimeException} and {@code Error} directly
* or wrap any other exception type into a {@code RuntimeException}.
* @param t the exception to throw directly or wrapped
* @return because {@code propagate} itself throws an exception or error, this is a sort of phantom return
* value; {@code propagate} does not actually return anything
*/
public static RuntimeException propagate(Throwable t) {
/*
* The return type of RuntimeException is a trick for code to be like this:
*
* throw Exceptions.propagate(e);
*
* Even though nothing will return and throw via that 'throw', it allows the code to look like it
* so it's easy to read and understand that it will always result in a throw.
*/
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else if (t instanceof Error) {
throw (Error) t;
} else {
throw new RuntimeException(t); // NOPMD
}
}
/**
* Throws a particular {@code Throwable} only if it belongs to a set of "fatal" error varieties. These
* varieties are as follows:
*
* - {@link OnErrorNotImplementedException}
* - {@link OnErrorFailedException}
* - {@link OnCompletedFailedException}
* - {@code VirtualMachineError}
* - {@code ThreadDeath}
* - {@code LinkageError}
*
* This can be useful if you are writing an operator that calls user-supplied code, and you want to
* notify subscribers of errors encountered in that code by calling their {@code onError} methods, but only
* if the errors are not so catastrophic that such a call would be futile, in which case you simply want to
* rethrow the error.
*
* @param t
* the {@code Throwable} to test and perhaps throw
* @see RxJava: StackOverflowError is swallowed (Issue #748)
*/
public static void throwIfFatal(Throwable t) {
if (t instanceof OnErrorNotImplementedException) {
throw (OnErrorNotImplementedException) t;
} else if (t instanceof OnErrorFailedException) {
throw (OnErrorFailedException) t;
} else if (t instanceof OnCompletedFailedException) {
throw (OnCompletedFailedException) t;
}
// values here derived from https://github.com/ReactiveX/RxJava/issues/748#issuecomment-32471495
else if (t instanceof VirtualMachineError) {
throw (VirtualMachineError) t;
} else if (t instanceof ThreadDeath) {
throw (ThreadDeath) t;
} else if (t instanceof LinkageError) {
throw (LinkageError) t;
}
}
/**
* Adds a {@code Throwable} to a causality-chain of Throwables, as an additional cause (if it does not
* already appear in the chain among the causes).
*
* @param e
* the {@code Throwable} at the head of the causality chain
* @param cause
* the {@code Throwable} you want to add as a cause of the chain
*/
public static void addCause(Throwable e, Throwable cause) {
Set seenCauses = new HashSet();
int i = 0;
while (e.getCause() != null) {
if (i++ >= MAX_DEPTH) {
// stack too deep to associate cause
return;
}
e = e.getCause();
if (seenCauses.contains(e.getCause())) {
break;
} else {
seenCauses.add(e.getCause());
}
}
// we now have 'e' as the last in the chain
try {
e.initCause(cause);
} catch (Throwable t) { // NOPMD
// ignore
// the javadocs say that some Throwables (depending on how they're made) will never
// let me call initCause without blowing up even if it returns null
}
}
/**
* Get the {@code Throwable} at the end of the causality-chain for a particular {@code Throwable}
*
* @param e
* the {@code Throwable} whose final cause you are curious about
* @return the last {@code Throwable} in the causality-chain of {@code e} (or a "Stack too deep to get
* final cause" {@code RuntimeException} if the chain is too long to traverse)
*/
public static Throwable getFinalCause(Throwable e) {
int i = 0;
while (e.getCause() != null) {
if (i++ >= MAX_DEPTH) {
// stack too deep to get final cause
return new RuntimeException("Stack too deep to get final cause");
}
e = e.getCause();
}
return e;
}
/**
* Throws a single or multiple exceptions contained in the collection, wrapping it into
* {@code CompositeException} if necessary.
* @param exceptions the collection of exceptions. If null or empty, no exception is thrown.
* If the collection contains a single exception, that exception is either thrown as-is or wrapped into a
* CompositeException. Multiple exceptions are wrapped into a CompositeException.
* @since 1.1.0
*/
public static void throwIfAny(List exceptions) {
if (exceptions != null && !exceptions.isEmpty()) {
if (exceptions.size() == 1) {
Throwable t = exceptions.get(0);
// had to manually inline propagate because some tests attempt StackOverflowError
// and can't handle it with the stack space remaining
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else if (t instanceof Error) {
throw (Error) t;
} else {
throw new RuntimeException(t); // NOPMD
}
}
throw new CompositeException(exceptions);
}
}
/**
* Forwards a fatal exception or reports it along with the value
* caused it to the given Observer.
* @param t the exception
* @param o the observer to report to
* @param value the value that caused the exception
* @since 1.3
*/
public static void throwOrReport(Throwable t, Observer o, Object value) {
Exceptions.throwIfFatal(t);
o.onError(OnErrorThrowable.addValueAsLastCause(t, value));
}
/**
* Forwards a fatal exception or reports it along with the value
* caused it to the given SingleSubscriber.
* @param t the exception
* @param o the observer to report to
* @param value the value that caused the exception
* @since 1.3
*/
public static void throwOrReport(Throwable t, SingleSubscriber o, Object value) {
Exceptions.throwIfFatal(t);
o.onError(OnErrorThrowable.addValueAsLastCause(t, value));
}
/**
* Forwards a fatal exception or reports it to the given Observer.
* @param t the exception
* @param o the observer to report to
* @since 1.3
*/
public static void throwOrReport(Throwable t, Observer o) {
Exceptions.throwIfFatal(t);
o.onError(t);
}
/**
* Forwards a fatal exception or reports it to the given Observer.
*
* @param throwable the exception.
* @param subscriber the subscriber to report to.
* @since 1.3
*/
public static void throwOrReport(Throwable throwable, SingleSubscriber subscriber) {
Exceptions.throwIfFatal(throwable);
subscriber.onError(throwable);
}
}