Copyright (c) 2016-present, RxJava Contributors.
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.
/**
* Copyright (c) 2016-present, RxJava Contributors.
*
* 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 io.reactivex.exceptions;
import java.io.*;
import java.util.*;
import io.reactivex.annotations.NonNull;
Represents an exception that is a composite of one or more other exceptions. A CompositeException
does not modify the structure of any exception it wraps, but at print-time it iterates through the list of Throwables contained in the composite in order to print them all. Its invariant is to contain an immutable, ordered (by insertion order), unique list of non-composite exceptions. You can retrieve individual exceptions in this list with getExceptions()
. The printStackTrace()
implementation handles the StackTrace in a customized way instead of using getCause()
so that it can avoid circular references. If you invoke getCause()
, it will lazily create the causal chain but will stop if it finds any Throwable in the chain that it has already seen. /**
* Represents an exception that is a composite of one or more other exceptions. A {@code CompositeException}
* does not modify the structure of any exception it wraps, but at print-time it iterates through the list of
* Throwables contained in the composite in order to print them all.
*
* Its invariant is to contain an immutable, ordered (by insertion order), unique list of non-composite
* exceptions. You can retrieve individual exceptions in this list with {@link #getExceptions()}.
*
* The {@link #printStackTrace()} implementation handles the StackTrace in a customized way instead of using
* {@code getCause()} so that it can avoid circular references.
*
* If you invoke {@link #getCause()}, it will lazily create the causal chain but will stop if it finds any
* Throwable in the chain that it has already seen.
*/
public final class CompositeException extends RuntimeException {
private static final long serialVersionUID = 3026362227162912146L;
private final List<Throwable> exceptions;
private final String message;
private Throwable cause;
Constructs a CompositeException with the given array of Throwables as the
list of suppressed exceptions.
Params: - exceptions – the Throwables to have as initially suppressed exceptions
Throws: - IllegalArgumentException – if
exceptions
is empty.
/**
* Constructs a CompositeException with the given array of Throwables as the
* list of suppressed exceptions.
* @param exceptions the Throwables to have as initially suppressed exceptions
*
* @throws IllegalArgumentException if <code>exceptions</code> is empty.
*/
public CompositeException(@NonNull Throwable... exceptions) {
this(exceptions == null ?
Collections.singletonList(new NullPointerException("exceptions was null")) : Arrays.asList(exceptions));
}
Constructs a CompositeException with the given array of Throwables as the
list of suppressed exceptions.
Params: - errors – the Throwables to have as initially suppressed exceptions
Throws: - IllegalArgumentException – if
errors
is empty.
/**
* Constructs a CompositeException with the given array of Throwables as the
* list of suppressed exceptions.
* @param errors the Throwables to have as initially suppressed exceptions
*
* @throws IllegalArgumentException if <code>errors</code> is empty.
*/
public CompositeException(@NonNull Iterable<? extends Throwable> errors) {
Set<Throwable> deDupedExceptions = new LinkedHashSet<Throwable>();
List<Throwable> localExceptions = new ArrayList<Throwable>();
if (errors != null) {
for (Throwable ex : errors) {
if (ex instanceof CompositeException) {
deDupedExceptions.addAll(((CompositeException) ex).getExceptions());
} else
if (ex != null) {
deDupedExceptions.add(ex);
} else {
deDupedExceptions.add(new NullPointerException("Throwable was null!"));
}
}
} else {
deDupedExceptions.add(new NullPointerException("errors was null"));
}
if (deDupedExceptions.isEmpty()) {
throw new IllegalArgumentException("errors is empty");
}
localExceptions.addAll(deDupedExceptions);
this.exceptions = Collections.unmodifiableList(localExceptions);
this.message = exceptions.size() + " exceptions occurred. ";
}
Retrieves the list of exceptions that make up the CompositeException
. Returns: the exceptions that make up the CompositeException
, as a List
of Throwable
s
/**
* Retrieves the list of exceptions that make up the {@code CompositeException}.
*
* @return the exceptions that make up the {@code CompositeException}, as a {@link List} of {@link Throwable}s
*/
@NonNull
public List<Throwable> getExceptions() {
return exceptions;
}
@Override
@NonNull
public String getMessage() {
return message;
}
@Override
@NonNull
public synchronized Throwable getCause() { // NOPMD
if (cause == null) {
// we lazily generate this causal chain if this is called
CompositeExceptionCausalChain localCause = new CompositeExceptionCausalChain();
Set<Throwable> seenCauses = new HashSet<Throwable>();
Throwable chain = localCause;
for (Throwable e : exceptions) {
if (seenCauses.contains(e)) {
// already seen this outer Throwable so skip
continue;
}
seenCauses.add(e);
List<Throwable> listOfCauses = getListOfCauses(e);
// check if any of them have been seen before
for (Throwable child : listOfCauses) {
if (seenCauses.contains(child)) {
// already seen this outer Throwable so skip
e = new RuntimeException("Duplicate found in causal chain so cropping to prevent loop ...");
continue;
}
seenCauses.add(child);
}
// we now have 'e' as the last in the chain
try {
chain.initCause(e);
} 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
}
chain = getRootCause(chain);
}
cause = localCause;
}
return cause;
}
All of the following printStackTrace
functionality is derived from JDK Throwable
printStackTrace
. In particular, the PrintStreamOrWriter
abstraction is copied wholesale. Changes from the official JDK implementation:
- no infinite loop detection
- smaller critical section holding
PrintStream
lock
- explicit knowledge about the exceptions
List
that this loops through
/**
* All of the following {@code printStackTrace} functionality is derived from JDK {@link Throwable}
* {@code printStackTrace}. In particular, the {@code PrintStreamOrWriter} abstraction is copied wholesale.
*
* Changes from the official JDK implementation:<ul>
* <li>no infinite loop detection</li>
* <li>smaller critical section holding {@link PrintStream} lock</li>
* <li>explicit knowledge about the exceptions {@link List} that this loops through</li>
* </ul>
*/
@Override
public void printStackTrace() {
printStackTrace(System.err);
}
@Override
public void printStackTrace(PrintStream s) {
printStackTrace(new WrappedPrintStream(s));
}
@Override
public void printStackTrace(PrintWriter s) {
printStackTrace(new WrappedPrintWriter(s));
}
Special handling for printing out a CompositeException
. Loops through all inner exceptions and prints them out. Params: - s –
stream to print to
/**
* Special handling for printing out a {@code CompositeException}.
* Loops through all inner exceptions and prints them out.
*
* @param s
* stream to print to
*/
private void printStackTrace(PrintStreamOrWriter s) {
StringBuilder b = new StringBuilder(128);
b.append(this).append('\n');
for (StackTraceElement myStackElement : getStackTrace()) {
b.append("\tat ").append(myStackElement).append('\n');
}
int i = 1;
for (Throwable ex : exceptions) {
b.append(" ComposedException ").append(i).append(" :\n");
appendStackTrace(b, ex, "\t");
i++;
}
s.println(b.toString());
}
private void appendStackTrace(StringBuilder b, Throwable ex, String prefix) {
b.append(prefix).append(ex).append('\n');
for (StackTraceElement stackElement : ex.getStackTrace()) {
b.append("\t\tat ").append(stackElement).append('\n');
}
if (ex.getCause() != null) {
b.append("\tCaused by: ");
appendStackTrace(b, ex.getCause(), "");
}
}
abstract static class PrintStreamOrWriter {
Prints the specified string as a line on this StreamOrWriter. /** Prints the specified string as a line on this StreamOrWriter. */
abstract void println(Object o);
}
Same abstraction and implementation as in JDK to allow PrintStream and PrintWriter to share implementation.
/**
* Same abstraction and implementation as in JDK to allow PrintStream and PrintWriter to share implementation.
*/
static final class WrappedPrintStream extends PrintStreamOrWriter {
private final PrintStream printStream;
WrappedPrintStream(PrintStream printStream) {
this.printStream = printStream;
}
@Override
void println(Object o) {
printStream.println(o);
}
}
static final class WrappedPrintWriter extends PrintStreamOrWriter {
private final PrintWriter printWriter;
WrappedPrintWriter(PrintWriter printWriter) {
this.printWriter = printWriter;
}
@Override
void println(Object o) {
printWriter.println(o);
}
}
static final class CompositeExceptionCausalChain extends RuntimeException {
private static final long serialVersionUID = 3875212506787802066L;
/* package-private */static final String MESSAGE = "Chain of Causes for CompositeException In Order Received =>";
@Override
public String getMessage() {
return MESSAGE;
}
}
private List<Throwable> getListOfCauses(Throwable ex) {
List<Throwable> list = new ArrayList<Throwable>();
Throwable root = ex.getCause();
if (root == null || root == ex) {
return list;
} else {
while (true) {
list.add(root);
Throwable cause = root.getCause();
if (cause == null || cause == root) {
return list;
} else {
root = cause;
}
}
}
}
Returns the number of suppressed exceptions.
Returns: the number of suppressed exceptions
/**
* Returns the number of suppressed exceptions.
* @return the number of suppressed exceptions
*/
public int size() {
return exceptions.size();
}
Returns the root cause of e
. If e.getCause()
returns null
or e
, just return e
itself. Params: - e – the
Throwable
e
.
Returns: The root cause of e
. If e.getCause()
returns null
or e
, just return e
itself.
/**
* Returns the root cause of {@code e}. If {@code e.getCause()} returns {@code null} or {@code e}, just return {@code e} itself.
*
* @param e the {@link Throwable} {@code e}.
* @return The root cause of {@code e}. If {@code e.getCause()} returns {@code null} or {@code e}, just return {@code e} itself.
*/
/*private */Throwable getRootCause(Throwable e) {
Throwable root = e.getCause();
if (root == null || e == root) {
return e;
}
while (true) {
Throwable cause = root.getCause();
if (cause == null || cause == root) {
return root;
}
root = cause;
}
}
}