Skip to content

Instantly share code, notes, and snippets.

@TheBrambleShark
Last active July 14, 2019 04:44
Show Gist options
  • Select an option

  • Save TheBrambleShark/a158ccec5d4dbea73dd14d28a3bbf1e6 to your computer and use it in GitHub Desktop.

Select an option

Save TheBrambleShark/a158ccec5d4dbea73dd14d28a3bbf1e6 to your computer and use it in GitHub Desktop.
A Java throwable which holds a collection of other throwables. Useful for when an operation throws multiple times.
/*
* MIT License
*
* Copyright (c) 2019 LuzFaltex
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.luzfaltex.sponge.titles.utilities;
import javax.annotation.Nonnull;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
public class AggregateThrowable extends Throwable {
private final List<Throwable> innerExceptions;
/**
* Initializes a new instance of the {@link AggregateThrowable} class.
*/
public AggregateThrowable() {
innerExceptions = Collections.unmodifiableList(new ArrayList<>());
}
/**
* Initializes a new instance of the {@link AggregateThrowable} class with
* a specified error message.
* @param message The error message that explains the reason for the exception.
*/
public AggregateThrowable(String message) {
super(message);
innerExceptions = Collections.unmodifiableList(new ArrayList<>());
}
/**
* Initializes a new instance of the {@Link AggregateThrowable} class with a specified error
* message and a reference to the inner exception that is the cause of this exception.
* @param message The error message that explains the reason for the exception.
* @param innerException The exception that is the cause of the current exception.
* @exception IllegalArgumentException The {@code innerException} argument is null.
*/
public AggregateThrowable(String message, @Nonnull Throwable innerException) throws IllegalArgumentException {
super(message);
if (null == innerException) throw new IllegalArgumentException();
List<Throwable> exceptions = new ArrayList<>();
exceptions.add(innerException);
innerExceptions = Collections.unmodifiableList(exceptions);
}
/**
* Initializes a new instance of the {@Link AggregateThrowable} class with
* references to the inner exceptions that are the cause of this exception.
* @param innerExceptions The exceptions that are the cause of the current exception.
* @throws IllegalArgumentException The {@code innerException} argument is null.
*/
public AggregateThrowable(Collection<Throwable> innerExceptions) throws IllegalArgumentException {
this("", innerExceptions);
}
/**
* Initializes a new instance of the {@Link AggregateThrowable} class with
* references to the inner exceptions that are the cause of this exception.
* @param innerExceptions The exceptions that are the cause of the current exception.
* @throws IllegalArgumentException The {@code innerException} argument is null.
*/
public AggregateThrowable(Throwable... innerExceptions) throws IllegalArgumentException {
this("", innerExceptions);
}
/**
* Initializes a new instance of the {@Link AggregateThrowable} class with
* references to the inner exceptions that are the cause of this exception.
* @param innerExceptions The exceptions that are the cause of the current exception.
* @throws IllegalArgumentException The {@code innerException} argument is null.
*/
public AggregateThrowable(String message, Collection<Throwable> innerExceptions) {
this("", innerExceptions.stream().collect(Collectors.toList()));
}
/**
* Initializes a new instance of the {@Link AggregateThrowable} class with
* references to the inner exceptions that are the cause of this exception.
* @param innerExceptions The exceptions that are the cause of the current exception.
* @throws IllegalArgumentException The {@code innerException} argument is null.
*/
public AggregateThrowable(String message, Throwable... innerExceptions) throws IllegalArgumentException {
this(message, Arrays.stream(innerExceptions).collect(Collectors.toList()));
}
/**
* Initializes a new instance of the {@Link AggregateThrowable} class with
* references to the inner exceptions that are the cause of this exception.
* @param innerExceptions The exceptions that are the cause of the current exception.
* @throws IllegalArgumentException The {@code innerException} argument is null.
* @throws IllegalArgumentException One of the innerExceptions is null.
*/
public AggregateThrowable(String message, List<Throwable> innerExceptions) {
super(message, (innerExceptions != null && innerExceptions.size() > 0) ? innerExceptions.get(0) : null);
if (innerExceptions == null) {
throw new IllegalArgumentException();
}
// Copy exceptions to our internal array and validate them. We must copy them,
// because we're going to put them into a ReadOnlyCollection which simply reuses
// the list passed into it. We don't want callers subsequently mutating.
Throwable[] exceptionsCopy = new Throwable[innerExceptions.size()];
for(int i =0; i < exceptionsCopy.length; i++) {
exceptionsCopy[i] = innerExceptions.get(i);
if (exceptionsCopy[i] == null) {
throw new IllegalArgumentException();
}
}
this.innerExceptions = Collections.unmodifiableList(Arrays.stream(exceptionsCopy).collect(Collectors.toList()));
}
/**
* Returns the {@link AggregateThrowable} that is the root cause of this exception
*/
public Throwable getBaseException() {
// Returns the first inner AggregateThrowable that contains more or less than one inner exception
// Recursively traverse the inner exceptions as long as the inner exception of type AggregageException and has only one inner exception
Optional<Throwable> back = Optional.of(this);
Optional<AggregateThrowable> backAsAggregate = Optional.of(this);
while (backAsAggregate.isPresent() && backAsAggregate.get().innerExceptions.size() == 1) {
back = Optional.of(back.get().getCause());
backAsAggregate = Optional.of((AggregateThrowable)back.get());
}
return back.get();
}
public List<Throwable> getInnerExceptions() {
return innerExceptions;
}
/**
* Invokes a handler on each {@link Throwable} contained by this {@link AggregateThrowable}.
* @param predicate The predicate to execute for each exception. The predicate accepts as an
* argument the {@link Throwable} to be processed and returns a {@code boolean} to indicate
* whether the exception was handled.
* @apiNote Each invocation of the {@code predicate} returns true or false to indicate whether the
* {@link Throwable} was handled. After all invocations, if any exceptions went
* unhandled, all unhandled exceptions will be put into a new {@link AggregateThrowable}
* which will be thrown. Otherwise, the {@link AggregateThrowable#handle(Function)} method simply returns. If any
* invocations of the {@code predicate} throws an exception, it will halt the processing of any more exceptions and
* immediately propogate the thrown exception as-is.
* @throws AggregateThrowable An exception contained by this {@link AggregateThrowable} was not handled.
* @throws IllegalArgumentException The {@code predicate} argument is null
*/
public void handle(Function<Throwable, Boolean> predicate) throws AggregateThrowable, IllegalArgumentException {
if (predicate == null) throw new IllegalArgumentException();
List<Throwable> unhandledExceptions = null;
for (int i = 0; i < innerExceptions.size(); i++) {
// If the exception was not handled, lazily allocate a list of unhandled
// exceptions (to be rethrown later) and add it
if (!predicate.apply(innerExceptions.get(i))) {
if (unhandledExceptions == null) {
unhandledExceptions = new ArrayList<>();
}
unhandledExceptions.add(innerExceptions.get(i));
}
}
if (unhandledExceptions != null) {
throw new AggregateThrowable(getMessage(), unhandledExceptions);
}
}
/**
* Flattens the inner instances of {@link AggregateThrowable} by expanding its contained {@link Throwable} instances
* into a new {@link AggregateThrowable}.
* @return A new, flattened {@link AggregateThrowable}.
* @apiNote If any inner exceptions are themselves instances of {@link AggregateThrowable},
* this method will recursively flatten all of them. The
* inner exceptions returned in the new {@link AggregateThrowable}
* will be the union of all of the inner exceptions from exception tree rooted at the provided
* {@link AggregateThrowable} instance.
*/
public AggregateThrowable flatten() {
// Initialize a collection to contain the flattened exceptions
List<Throwable> flattenedExceptions = new ArrayList<>();
// Create a list to remember all of the aggregates to be flattened. This will be accessed like a FIFO queue
List<AggregateThrowable> exceptionsToFlatten = new ArrayList<>();
exceptionsToFlatten.add(this);
int nDequeueIndex = 0;
// Continue removing and recursively flattening exceptions, until there are no more.
while (exceptionsToFlatten.size() > nDequeueIndex) {
// dequeue one from exceptionsToFlatten
List<Throwable> currentInnerExceptions = exceptionsToFlatten.get(nDequeueIndex++).innerExceptions;
for (int i = 0; i < currentInnerExceptions.size(); i++) {
Throwable currentInnerException = currentInnerExceptions.get(i);
if (currentInnerException == null) continue;;
// if this exception is an aggregate, keep it around for later. Otherwise,
// simply add it to the list of flattened exceptions to be returned.
if (currentInnerException instanceof AggregateThrowable) {
AggregateThrowable currentInnerAsAggregate = (AggregateThrowable)currentInnerException;
exceptionsToFlatten.add(currentInnerAsAggregate);
} else {
flattenedExceptions.add(currentInnerException);
}
}
}
return new AggregateThrowable(getMessage(), flattenedExceptions);
}
@Override
public String getMessage() {
// return super.getMessage();
if (innerExceptions.size() == 0) {
return super.getMessage();
}
StringBuilder sb = new StringBuilder();
sb.append(super.getMessage());
sb.append(' ');
for (int i = 0; i < innerExceptions.size(); i++) {
sb.append('(');
sb.append(innerExceptions.get(i));
sb.append(") ");
}
sb.setLength(sb.length() - 1);
return sb.toString();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
String newLine = System.lineSeparator();
for (int i = 0; i < innerExceptions.size(); i++) {
sb.append(newLine);
sb.append("---> ");
sb.append("(Inner Exception #" + i + ")");
sb.append(innerExceptions.get(i).toString());
sb.append("<---");
sb.append(newLine);
}
return sb.toString();
}
@Override
public StackTraceElement[] getStackTrace() {
// Get a list of all stack trace elements.
List<StackTraceElement> stElements = new ArrayList<>();
// Add the parent stack trace.
for (StackTraceElement element : super.getStackTrace()) {
}
return super.getStackTrace();
}
}
/*
* MIT License
*
* Copyright (c) 2019 LuzFaltex
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
class Consumer(private val cars: Collection<Car>) {
fun startAllEngines() : AggregateThrowable {
val throwables: ArrayList<Throwable> = arrayListOf()
for (car in cars) {
try {
car.startEngine()
} catch (throwable t) {
// Just because one car fails to start doesn't mean all of them do.
// But we don't want to swallow the exception either.
throwables.add(t)
}
}
if (throwables.isNotEmpty()) {
throw AggregateThrowable(throwables)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment