Last active
July 14, 2019 04:44
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * 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(); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * 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