├── .gitignore ├── src ├── main │ └── java │ │ └── io │ │ └── meat │ │ ├── CheckedSupplier.java │ │ ├── CheckedFunction.java │ │ └── Try.java └── test │ └── java │ └── io │ └── meat │ ├── TryFutureTest.java │ ├── CheckedSupplierTest.java │ ├── CheckedFunctionTest.java │ └── TryTest.java ├── UNLICENSE ├── README.md └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | Try.iml 2 | target 3 | -------------------------------------------------------------------------------- /src/main/java/io/meat/CheckedSupplier.java: -------------------------------------------------------------------------------- 1 | package io.meat; 2 | 3 | import java.util.function.Supplier; 4 | 5 | /** 6 | * A {@link java.util.function.Supplier} which can throw any exception. 7 | * 8 | *

Use the {@link #toUnchecked()} method to get a standard Supplier that 9 | * wraps any checked exceptions thrown by {@link #get()} in a 10 | * {@link RuntimeException} (if they're not already RuntimeExceptions).

11 | * 12 | * @param the type of value produced by this Supplier 13 | */ 14 | @FunctionalInterface 15 | public interface CheckedSupplier { 16 | T get() throws Exception; 17 | 18 | default Supplier toUnchecked() { 19 | return () -> { 20 | try { 21 | return get(); 22 | } catch (Exception e) { 23 | if (e instanceof RuntimeException) { 24 | throw (RuntimeException) e; 25 | } else { 26 | throw new RuntimeException(e); 27 | } 28 | } 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/meat/CheckedFunction.java: -------------------------------------------------------------------------------- 1 | package io.meat; 2 | 3 | import java.util.function.Function; 4 | 5 | /** 6 | * A {@link java.util.function.Function} which can throw any exception. 7 | * 8 | *

Use the {@link #toUnchecked()} method to get a standard Function that 9 | * wraps any checked exceptions thrown by the function in a 10 | * {@link RuntimeException} (if they're not already RuntimeExceptions).

11 | * 12 | * @param the input type of the function 13 | * @param the output type of the function 14 | */ 15 | @FunctionalInterface 16 | public interface CheckedFunction { 17 | R apply(T t) throws Exception; 18 | 19 | default Function toUnchecked() { 20 | return (input) -> { 21 | try { 22 | return apply(input); 23 | } catch (Exception e) { 24 | if (e instanceof RuntimeException) { 25 | throw (RuntimeException) e; 26 | } else { 27 | throw new RuntimeException(e); 28 | } 29 | } 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/io/meat/TryFutureTest.java: -------------------------------------------------------------------------------- 1 | package io.meat; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | 9 | public class TryFutureTest { 10 | @Test 11 | public void tryWrapCompletableFutureSucceeds() throws Exception { 12 | CompletableFuture future = new CompletableFuture<>(); 13 | CompletableFuture> wrappedFuture = Try.wrapFuture(future); 14 | future.complete("It worked!"); 15 | assertEquals("A successful wrapped future should contain the future's result as a successful Try", 16 | Try.succeed("It worked!"), 17 | wrappedFuture.get()); 18 | } 19 | 20 | @Test 21 | public void tryWrapCompletableFutureFails() throws Exception { 22 | IllegalStateException error = new IllegalStateException("Something broke!"); 23 | CompletableFuture future = new CompletableFuture<>(); 24 | CompletableFuture> wrappedFuture = Try.wrapFuture(future); 25 | future.completeExceptionally(error); 26 | assertEquals("A failed wrapped future should contain a failed Try", 27 | Try.fail(error), 28 | wrappedFuture.get()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /src/test/java/io/meat/CheckedSupplierTest.java: -------------------------------------------------------------------------------- 1 | package io.meat; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | import java.util.function.Supplier; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | import static org.junit.Assert.fail; 10 | 11 | public class CheckedSupplierTest { 12 | 13 | @Test 14 | public void testToUncheckedWrapsCheckedExceptions() throws Exception { 15 | CheckedSupplier func = () -> { 16 | throw new IOException("Hello"); 17 | }; 18 | Supplier unchecked = func.toUnchecked(); 19 | try { 20 | unchecked.get(); 21 | fail("Calling the Supplier should have raised an exception"); 22 | } catch (RuntimeException e) { 23 | assertEquals( 24 | "The cause of the RuntimeException should be the unchecked exception", 25 | e.getCause().getClass(), 26 | IOException.class); 27 | } 28 | } 29 | 30 | @Test 31 | public void testToUncheckedDoesntWrapUncheckedExceptions() throws Exception { 32 | CheckedSupplier func = () -> { 33 | throw new ArithmeticException("Hello"); 34 | }; 35 | Supplier unchecked = func.toUnchecked(); 36 | try { 37 | unchecked.get(); 38 | fail("Calling the Supplier should have raised an exception"); 39 | } catch (RuntimeException e) { 40 | assertEquals( 41 | "The exception should not be wrapped in another RuntimeException", 42 | e.getClass(), 43 | ArithmeticException.class); 44 | } 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/test/java/io/meat/CheckedFunctionTest.java: -------------------------------------------------------------------------------- 1 | package io.meat; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | import java.util.function.Function; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | import static org.junit.Assert.fail; 10 | 11 | public class CheckedFunctionTest { 12 | 13 | @Test 14 | public void testToUncheckedWrapsCheckedExceptions() throws Exception { 15 | CheckedFunction func = integer -> { 16 | throw new IOException("Hello"); 17 | }; 18 | Function unchecked = func.toUnchecked(); 19 | try { 20 | unchecked.apply(123); 21 | fail("Applying the function should have raised an exception"); 22 | } catch (RuntimeException e) { 23 | assertEquals( 24 | "The cause of the RuntimeException should be the unchecked exception", 25 | e.getCause().getClass(), 26 | IOException.class); 27 | } 28 | } 29 | 30 | @Test 31 | public void testToUncheckedDoesntWrapUncheckedExceptions() throws Exception { 32 | CheckedFunction func = integer -> { 33 | throw new ArithmeticException("Hello"); 34 | }; 35 | Function unchecked = func.toUnchecked(); 36 | try { 37 | unchecked.apply(123); 38 | fail("Applying the function should have raised an exception"); 39 | } catch (RuntimeException e) { 40 | assertEquals( 41 | "The exception should not be wrapped in another RuntimeException", 42 | e.getClass(), 43 | ArithmeticException.class); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # io.meat:try 2 | ### A humble Try monad for Java 8 3 | 4 | A Try is a completed attempt to compute a result that either succeeded or 5 | failed. 6 | 7 | ## Quickstart 8 | 9 | Try is especially useful for monadic composition of tasks that may each fail, 10 | without having to use a series of nested try/catch statements. You can form a 11 | Try using `attempt()` or `attemptApply()`: 12 | 13 | ```java 14 | Try value = Try.attempt(() -> keyValueStore.fetch("someKey")); 15 | Try value = Try.attemptApply(keyValueStore::fetch, "someKey"); 16 | ``` 17 | 18 | If the function has checked exceptions, you can use `attemptChecked()` or 19 | `attemptApplyChecked()` to wrap any exception in a RuntimeException. 20 | 21 | You can also manually make a successful or failed Try using `succeed()` and 22 | `fail()`: 23 | 24 | ```java 25 | Try value = Try.succeed("someValue"); 26 | Try failure = Try.fail(new IllegalStateException("Something broke")); 27 | ``` 28 | 29 | Once you have a Try, you can transform the value inside it using `map()`: 30 | 31 | ```java 32 | Try hex = Try.succeed(123).map(Integer::toHexString); 33 | assert hex.equals(Try.succeed("7b")); 34 | ``` 35 | 36 | If your function returns a Try, you can use `flatMap()` to prevent nesting: 37 | 38 | ```java 39 | Try number = Try.succeed(123); 40 | Try nextValue = number.flatMap(num -> { 41 | return Try.succeed("a string"); 42 | }); 43 | assert nextValue.equals(Try.succeed("a string")); 44 | ``` 45 | 46 | To get values back out of a Try, there are Optionals available as `getResult()` 47 | and `getFailure()` for safe retrieval of either state: 48 | 49 | ```java 50 | Try number = Try.succeed(123); 51 | assert number.getResult().equals(Optional.of(123)); 52 | assert !number.getFailure().isPresent(); 53 | ``` 54 | 55 | If you've checked that a Try is successful, or you don't mind an (unchecked) 56 | exception being raised, use `get()`: 57 | 58 | ```java 59 | Try number = Try.succeed(123); 60 | assert number.get() == 123; 61 | ``` 62 | 63 | In the case of a failed Try, `get()` will wrap the exception in a 64 | `RuntimeException`; its `Throwable.getCause()` will be the original exception. 65 | 66 | ## API Docs 67 | 68 | The API docs are hosted [on Github Pages](http://zacharyvoase.github.io/try/apidocs/). 69 | 70 | 71 | ## Unlicense 72 | 73 | This is free and unencumbered software released into the public domain. 74 | 75 | Anyone is free to copy, modify, publish, use, compile, sell, or 76 | distribute this software, either in source code form or as a compiled 77 | binary, for any purpose, commercial or non-commercial, and by any 78 | means. 79 | 80 | In jurisdictions that recognize copyright laws, the author or authors 81 | of this software dedicate any and all copyright interest in the 82 | software to the public domain. We make this dedication for the benefit 83 | of the public at large and to the detriment of our heirs and 84 | successors. We intend this dedication to be an overt act of 85 | relinquishment in perpetuity of all present and future rights to this 86 | software under copyright law. 87 | 88 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 89 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 90 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 91 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 92 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 93 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 94 | OTHER DEALINGS IN THE SOFTWARE. 95 | 96 | For more information, please refer to 97 | 98 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | io.meat 6 | try 7 | 0.0.2 8 | jar 9 | 10 | try 11 | A Try monad for Java 8 12 | https://github.com/zacharyvoase/try 13 | 14 | 15 | 16 | Zachary Voase 17 | zack@meat.io 18 | https://twitter.com/meat 19 | 20 | 21 | 22 | 23 | scm:git:git@github.com:zacharyvoase/try.git 24 | scm:git:git@github.com:zacharyvoase/try.git 25 | git@github.com:zacharyvoase/try.git 26 | 27 | 28 | 29 | 30 | Unlicense 31 | http://unlicense.org/UNLICENSE 32 | repo 33 | This is free and unencumbered software released into the public domain. 34 | 35 | 36 | 37 | 38 | 39 | ossrh 40 | https://oss.sonatype.org/content/repositories/snapshots 41 | 42 | 43 | 44 | 45 | UTF-8 46 | 47 | 48 | 49 | 50 | junit 51 | junit 52 | 4.12 53 | test 54 | 55 | 56 | 57 | 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-compiler-plugin 62 | 3.3 63 | 64 | 1.8 65 | 1.8 66 | 67 | 68 | 69 | 70 | org.apache.maven.plugins 71 | maven-site-plugin 72 | 3.3 73 | 74 | true 75 | 76 | 77 | 78 | 79 | org.apache.maven.plugins 80 | maven-source-plugin 81 | 82 | 83 | attach-sources 84 | 85 | jar 86 | 87 | 88 | 89 | 90 | 91 | 92 | org.apache.maven.plugins 93 | maven-javadoc-plugin 94 | 2.10.3 95 | 96 | 97 | attach-javadocs 98 | 99 | jar 100 | 101 | 102 | 103 | 104 | 105 | 106 | com.github.github 107 | site-maven-plugin 108 | 0.12 109 | 110 | github 111 | Updated JavaDocs for ${project.version} 112 | 113 | 114 | 115 | 116 | site 117 | 118 | site-deploy 119 | 120 | 121 | 122 | 123 | 124 | org.apache.maven.plugins 125 | maven-gpg-plugin 126 | 1.6 127 | 128 | 129 | sign-artifacts 130 | verify 131 | 132 | sign 133 | 134 | 135 | 136 | 137 | 138 | 139 | org.sonatype.plugins 140 | nexus-staging-maven-plugin 141 | 1.6.3 142 | true 143 | 144 | ossrh 145 | https://oss.sonatype.org/ 146 | true 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | org.apache.maven.plugins 156 | maven-project-info-reports-plugin 157 | 2.8 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | org.apache.maven.plugins 168 | maven-javadoc-plugin 169 | 2.10.3 170 | 171 | ${project.reporting.outputDirectory} 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /src/main/java/io/meat/Try.java: -------------------------------------------------------------------------------- 1 | package io.meat; 2 | 3 | import java.util.Objects; 4 | import java.util.Optional; 5 | import java.util.concurrent.CompletableFuture; 6 | import java.util.function.Function; 7 | import java.util.function.Supplier; 8 | import java.util.stream.Stream; 9 | 10 | /** 11 | * A completed attempt to compute a result that either succeeded or failed. 12 | * 13 | *

Try is especially useful for monadic composition of tasks that may either 14 | * succeed or fail, without having to use a series of nested try/catch 15 | * statements. You can form a Try using {@link #attempt(Supplier)} or 16 | * {@link #attemptApply(Function, Object)}:

17 | * 18 | *
{@code
 19 |  * Try value = Try.attempt(() -> keyValueStore.fetch("someKey"));
 20 |  * Try value = Try.attemptApply(keyValueStore::fetch, "someKey");
 21 |  * }
22 | * 23 | *

If the function has checked exceptions, you can use 24 | * {@link #attemptChecked(CheckedSupplier)} or 25 | * {@link #attemptApplyChecked(CheckedFunction, Object)} to wrap any exception 26 | * in a RuntimeException.

27 | * 28 | *

You can also manually make a successful or failed Try using 29 | * {@link #succeed(Object)} and {@link #fail(Throwable)}:

30 | * 31 | *
{@code
 32 |  * Try value = Try.succeed("someValue");
 33 |  * Try failure = Try.fail(new IllegalStateException("Something broke"));
 34 |  * }
35 | * 36 | *

Once you have a Try, you can transform the value inside it using 37 | * {@link #map(Function)}:

38 | * 39 | *
{@code
 40 |  * Try hex = Try.succeed(123).map(Integer::toHexString);
 41 |  * assert hex.equals(Try.succeed("7b"));
 42 |  * }
43 | * 44 | *

If your function returns a Try, you can use {@link #flatMap(Function)} to 45 | * prevent nesting:

46 | * 47 | *
{@code
 48 |  * Try number = Try.succeed(123);
 49 |  * Try nextValue = number.flatMap(num -> {
 50 |  *     return Try.succeed("a string");
 51 |  * });
 52 |  * assert nextValue.equals(Try.succeed("a string"));
 53 |  * }
54 | * 55 | *

To get values back out of a Try, there are Optionals available as 56 | * {@link #getResult()} and {@link #getFailure()} for safe retrieval of either 57 | * state:

58 | * 59 | *
{@code
 60 |  * Try number = Try.succeed(123);
 61 |  * assert number.getResult().equals(Optional.of(123));
 62 |  * assert !number.getFailure().isPresent();
 63 |  * }
64 | * 65 | *

If you've checked that a Try is successful, or you don't mind an (unchecked) 66 | * exception being raised, use {@link #get()}:

67 | * 68 | *
{@code
 69 |  * Try number = Try.succeed(123);
 70 |  * assert number.get() == 123;
 71 |  * }
72 | * 73 | *

In the case of a failed Try, get() will wrap the exception in a 74 | * RuntimeException; its {@link Throwable#getCause()} will be the 75 | * original exception.

76 | * 77 | * @param the type of result, if successful. 78 | */ 79 | public final class Try { 80 | 81 | private final Result result; 82 | private final Throwable failure; 83 | 84 | private Try(Result result, Throwable failure) { 85 | assert (result == null) ^ (failure == null) 86 | : "Exactly one of failure or result must be null"; 87 | this.result = result; 88 | this.failure = failure; 89 | } 90 | 91 | /** 92 | * Build a successful Try from a non-null result. 93 | */ 94 | public static Try succeed(Result result) { 95 | if (result == null) { 96 | throw new IllegalArgumentException("Try.succeed result may not be null"); 97 | } 98 | return new Try<>(result, null); 99 | } 100 | 101 | /** 102 | * Build a failed Try from an exception or other Throwable. 103 | */ 104 | public static Try fail(Throwable failure) { 105 | if (failure == null) { 106 | throw new IllegalArgumentException("Try.fail failure may not be null"); 107 | } 108 | return new Try<>(null, failure); 109 | } 110 | 111 | /** 112 | * Wrap the result of a {@link Supplier} as a Try, catching exceptions. 113 | * 114 | * @param func a Supplier which returns a Result 115 | * @param the type of result returned by func 116 | * @return a Try containing either the Supplier's result, or any exception 117 | * thrown by {@link Supplier#get()} 118 | */ 119 | public static Try attempt(Supplier func) { 120 | return attemptChecked(func::get); 121 | } 122 | 123 | /** 124 | * Similar to {@link #attempt(Supplier)}, but handles checked exceptions. 125 | * @param func a CheckedSupplier which returns a Result or throws Exception 126 | * @param the type of result returned by func 127 | * @return a Try containing either the CheckedSupplier's result, or any 128 | * exception thrown by {@link CheckedSupplier#get()} 129 | */ 130 | public static Try attemptChecked(CheckedSupplier func) { 131 | Result result; 132 | try { 133 | result = func.get(); 134 | } catch (Exception e) { 135 | return Try.fail(e); 136 | } 137 | return Try.succeed(result); 138 | } 139 | 140 | 141 | /** 142 | * Wrap the result of a {@link Function} as a Try, catching exceptions. 143 | * 144 | * @param func a Function from the input to the result type of the Try 145 | * @param input a single argument for func 146 | * @param the input type of the function 147 | * @param the result type of the function 148 | * @return a Try of type Result 149 | */ 150 | public static Try attemptApply( 151 | Function func, 152 | Input input) { 153 | return attemptApplyChecked(func::apply, input); 154 | } 155 | 156 | /** 157 | * Similar to {@link #attemptApply(Function, Object)}, handling checked exceptions. 158 | * 159 | * @param func a Function from the input to the result type of the Try 160 | * @param input a single argument for func 161 | * @param the input type of the function 162 | * @param the result type of the function 163 | * @return a Try of type Result 164 | */ 165 | public static Try attemptApplyChecked( 166 | CheckedFunction func, 167 | Input input) { 168 | Result result; 169 | try { 170 | result = func.apply(input); 171 | } catch (Exception e) { 172 | return Try.fail(e); 173 | } 174 | return Try.succeed(result); 175 | } 176 | 177 | public static CompletableFuture> wrapFuture(CompletableFuture future) { 178 | return future.handle(Try::new); 179 | } 180 | 181 | /** 182 | * Get the successful result of this Try, or {@link Optional#empty()} if it failed. 183 | * 184 | * @return an Optional result 185 | */ 186 | public Optional getResult() { 187 | return Optional.ofNullable(result); 188 | } 189 | 190 | /** 191 | * Get the Throwable that caused this Try to fail, or {@link Optional#empty()} if it was successful. 192 | * 193 | * @return an Optional Throwable 194 | */ 195 | public Optional getFailure() { 196 | return Optional.ofNullable(failure); 197 | } 198 | 199 | /** 200 | * Get this Try's result, or throw its failure as an unchecked exception. 201 | * 202 | * @return the successful result of this Try 203 | * @throws RuntimeException wrapping the failure as an unchecked exception 204 | */ 205 | public Result get() { 206 | if (result == null) { 207 | throw new RuntimeException(failure); 208 | } 209 | return result; 210 | } 211 | 212 | /** 213 | * Turn this Try into a Stream, for flatMapping a {@code Stream>} into a {@code Stream}. 214 | * 215 | *

This method is particularly useful as an argument to Stream#flatMap():

216 | * 217 | *
{@code
218 |      * Stream strings = Stream.of("123", "456", "abc", "789");
219 |      * Stream numbers = strings.map(s -> Try.attemptApply(Integer::parseInt, s)).flatMap(Try::stream);
220 |      * assert Arrays.equals(numbers.toArray(), Stream.of(123, 456, 789).toArray());
221 |      * }
222 | * 223 | * @return A single-element Stream.of(result) if this Try is successful, otherwise Stream.empty() 224 | */ 225 | public Stream stream() { 226 | if (result == null) { 227 | return Stream.empty(); 228 | } 229 | return Stream.of(result); 230 | } 231 | 232 | /** 233 | * Transform the result of this Try. 234 | * 235 | *

If this Try is successful, the provided function will be applied to 236 | * the current result and a new Try of the destination type will be 237 | * returned.

238 | *

If this Try is a failure, a new Try of the destination result type 239 | * containing the existing failure will be returned.

240 | *

If this Try is successful but the mapping function throws an 241 | * exception, a failed Try of the destination result type will be 242 | * returned, containing that exception.

243 | * 244 | * @param func A function mapping this Try's result type to a new one 245 | * @param the output type of the transformation function 246 | * @return a new Try of either the transformed result or existing failure 247 | */ 248 | public Try map(Function func) { 249 | if (result == null) { return Try.fail(this.failure); } 250 | return Try.attemptApply(func, result); 251 | } 252 | 253 | /** 254 | * Transform the result of this Try into a new Try, returning that. 255 | * 256 | *

The behavior of this function is similar to {@link #map(Function)}, 257 | * except that func should return a Try, and this will be returned without 258 | * being wrapped further.

259 | *

Any uncaught exceptions thrown by func will themselves be captured in 260 | * a Try.

261 | * 262 | * @param func a function mapping this Try's result type to another Try 263 | * @param the result type of the Try produced by func 264 | * @return a Try of the destination result type 265 | */ 266 | public Try flatMap(Function> func) { 267 | if (result == null) { return Try.fail(this.failure); } 268 | // This is kind of like Try.attemptApply but we don't wrap the result 269 | // of func.apply in Try.succeed. 270 | try { 271 | return func.apply(result); 272 | } catch (Exception e) { 273 | return Try.fail(e); 274 | } 275 | } 276 | 277 | @Override 278 | public boolean equals(Object o) { 279 | if (this == o) return true; 280 | if (o == null || getClass() != o.getClass()) return false; 281 | Try aTry = (Try) o; 282 | return Objects.equals(result, aTry.result) && 283 | Objects.equals(failure, aTry.failure); 284 | } 285 | 286 | @Override 287 | public int hashCode() { 288 | return Objects.hash(result, failure); 289 | } 290 | 291 | @Override 292 | public String toString() { 293 | if (result != null) { 294 | return "Try{result=" + result + "}"; 295 | } else { 296 | return "Try{failure=" + failure + "}"; 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/test/java/io/meat/TryTest.java: -------------------------------------------------------------------------------- 1 | package io.meat; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | import java.util.function.Function; 7 | import java.util.stream.Stream; 8 | 9 | import static org.junit.Assert.*; 10 | 11 | public class TryTest { 12 | @Test(expected = IllegalArgumentException.class) 13 | public void testCannotSucceedWithNull() { 14 | Try.succeed(null); 15 | } 16 | 17 | @Test(expected = IllegalArgumentException.class) 18 | public void testCannotFailWithNull() { 19 | Try.fail(null); 20 | } 21 | 22 | @Test 23 | public void testResultAndFailureForSuccess() { 24 | Try successful = Try.succeed(123); 25 | assertTrue( 26 | "getResult() of a successful Try should be a non-empty Optional", 27 | successful.getResult().isPresent()); 28 | assertEquals(successful.getResult().get(), (Integer) 123); 29 | assertFalse( 30 | "getFailure() of a successful Try should be an empty Optional", 31 | successful.getFailure().isPresent()); 32 | } 33 | 34 | @Test 35 | public void testResultAndFailureForFailure() { 36 | Exception error = new IllegalStateException("Something went wrong"); 37 | Try failed = Try.fail(error); 38 | assertFalse( 39 | "getResult() of a failed Try should be an empty Optional", 40 | failed.getResult().isPresent()); 41 | assertTrue( 42 | "getFailure() of a failed Try should be a non-empty Optional", 43 | failed.getFailure().isPresent()); 44 | assertEquals(failed.getFailure().get(), error); 45 | } 46 | 47 | @Test 48 | public void testAttemptReturnsSuccess() { 49 | Try successful = Try.attempt(() -> 123); 50 | assertEquals( 51 | "Try.attempt() should return a successful Try for a successful attempt", 52 | Try.succeed(123), successful); 53 | } 54 | 55 | @Test 56 | public void testAttemptCapturesRuntimeExceptions() { 57 | IllegalStateException error = new IllegalStateException("Things broke"); 58 | Try failed = Try.attempt(() -> { 59 | if (true) { 60 | throw error; 61 | } else { 62 | // this will never happen 63 | return 123; 64 | } 65 | }); 66 | assertEquals( 67 | "Try.attempt() should return a failed Try for an uncaught exception", 68 | Try.fail(error), failed); 69 | } 70 | 71 | @Test 72 | public void testAttemptCheckedReturnsSuccess() { 73 | Try successful = Try.attemptChecked(() -> 123); 74 | assertEquals( 75 | "Try.attemptChecked() should return a successful Try for a successful attempt", 76 | Try.succeed(123), successful); 77 | } 78 | 79 | @Test 80 | public void testAttemptCheckedCapturesCheckedExceptions() { 81 | IOException error = new IOException("Things broke"); 82 | Try failed = Try.attemptChecked(() -> { 83 | if (true) { 84 | throw error; 85 | } else { 86 | // this will never happen 87 | return 123; 88 | } 89 | }); 90 | assertEquals( 91 | "Try.attemptChecked() should return a failed Try for a checked exception", 92 | Try.fail(error), failed); 93 | } 94 | 95 | @Test 96 | public void testAttemptApplyReturnsSuccess() { 97 | Try successful = Try.attemptApply(Integer::toHexString, 123); 98 | assertEquals( 99 | "Try.attemptApply() should return a successful Try for a successful application", 100 | Try.succeed("7b"), successful); 101 | } 102 | 103 | @Test 104 | public void testAttemptApplyCapturesRuntimeExceptions() { 105 | Try failed = Try.attemptApply(number -> number / 0, 123); 106 | assertEquals( 107 | "Try.attemptApply() should return a failed Try for an uncaught exception", 108 | ArithmeticException.class, 109 | failed.getFailure().get().getClass()); 110 | } 111 | 112 | @Test 113 | public void testAttemptApplyCheckedReturnsSuccess() { 114 | Try successful = Try.attemptApplyChecked(Integer::toHexString, 123); 115 | assertEquals( 116 | "Try.attemptApplyChecked() should return a successful Try for a successful application", 117 | Try.succeed("7b"), successful); 118 | } 119 | 120 | @Test 121 | public void testAttemptApplyCheckedCapturesCheckedExceptions() { 122 | IOException error = new IOException("Things broke"); 123 | Try failed = Try.attemptApplyChecked(number -> { 124 | if (true) { 125 | throw error; 126 | } else { 127 | return "this will never happen"; 128 | } 129 | }, 123); 130 | assertEquals( 131 | "Try.attemptApplyChecked() should return a failed Try for a checked exception", 132 | Try.fail(error), failed); 133 | } 134 | 135 | @Test 136 | public void testGetShouldReturnResultForSuccess() { 137 | String value = "A successful value"; 138 | assertEquals( 139 | "Try.get() should return the result for a successful Try", 140 | value, 141 | Try.succeed(value).get()); 142 | } 143 | 144 | @Test 145 | public void testGetShouldThrowAnUncheckedExceptionForFailure() { 146 | ArithmeticException error = new ArithmeticException("Probably divided by zero"); 147 | try { 148 | Try.fail(error).get(); 149 | fail("Try.get() should throw an unchecked exception for a failed Try"); 150 | } catch (Exception e) { 151 | assertEquals( 152 | "Try.get() should throw a RuntimeException", 153 | RuntimeException.class, 154 | e.getClass()); 155 | assertEquals( 156 | "Try.get() should throw a RuntimeException caused by the failure of the Try", 157 | error, 158 | e.getCause()); 159 | } 160 | } 161 | 162 | @Test 163 | public void testStreamShouldRemoveFailedTrys() { 164 | Stream> numberTries = Stream.of("123", "456", "abc", "789") 165 | .map(s -> Try.attemptApply(Integer::parseInt, s)); 166 | Stream numbers = Stream.of("123", "456", "abc", "789") 167 | .map(s -> Try.attemptApply(Integer::parseInt, s)) 168 | .flatMap(Try::stream); 169 | assertEquals("The original mapped stream should contain both failed and successful Trys", 170 | 4, 171 | numberTries.toArray().length); 172 | assertArrayEquals("A flatMapped stream of Trys should not contain entries for failures", 173 | Stream.of(123, 456, 789).toArray(), 174 | numbers.toArray()); 175 | } 176 | 177 | @Test 178 | public void testTrySuccessString() { 179 | assertEquals("Try{result=123}", Try.succeed(123).toString()); 180 | } 181 | 182 | @Test 183 | public void testTryFailureString() { 184 | Exception error = new IllegalStateException("Some error"); 185 | assertEquals( 186 | "Try{failure=java.lang.IllegalStateException: Some error}", 187 | Try.fail(error).toString()); 188 | } 189 | 190 | @Test 191 | public void testTryHashCode() { 192 | Try result1 = Try.succeed(123); 193 | Try result2 = Try.succeed("Hello"); 194 | Try result3 = Try.succeed("Hello"); 195 | Try result4 = Try.succeed("World"); 196 | Exception error1 = new ArithmeticException("Things got divided by zero"); 197 | Exception error2 = new IllegalStateException("Things broke"); 198 | Try failure1 = Try.fail(error1); 199 | Try failure2 = Try.fail(error2); 200 | Try failure3 = Try.fail(error1); 201 | Try failure4 = Try.fail(error1); 202 | 203 | assertEquals("Identical successful Trys should have the same hashCode", 204 | result2.hashCode(), result2.hashCode()); 205 | assertEquals("Same type and equal but distinct successful Trys should have the same hashCode", 206 | result2.hashCode(), result3.hashCode()); 207 | assertNotEquals("Same type but unequal successful Trys should not have the same hashCode", 208 | result3.hashCode(), result4.hashCode()); 209 | assertNotEquals("Differently-typed successful Trys should not have the same hashCode", 210 | result1.hashCode(), result2.hashCode()); 211 | 212 | assertEquals("Same type failed Trys with equal exceptions should have the same hashCode", 213 | failure1.hashCode(), failure4.hashCode()); 214 | assertEquals("Differently-typed failed Trys with equal exceptions should have the same hashCode", 215 | failure1.hashCode(), failure3.hashCode()); 216 | assertNotEquals("Same-typed failed Trys with different exceptions should not have the same hashCode", 217 | failure1.hashCode(), failure2.hashCode()); 218 | assertNotEquals("Differently-typed failed Trys with different exceptions should not have the same hashCode", 219 | failure2.hashCode(), failure3.hashCode()); 220 | } 221 | 222 | @Test 223 | public void testTryEquality() { 224 | Try result1 = Try.succeed(123); 225 | Try result2 = Try.succeed("Hello"); 226 | Try result3 = Try.succeed("Hello"); 227 | Try result4 = Try.succeed("World"); 228 | Exception error1 = new ArithmeticException("Things got divided by zero"); 229 | Exception error2 = new IllegalStateException("Things broke"); 230 | Try failure1 = Try.fail(error1); 231 | Try failure2 = Try.fail(error2); 232 | Try failure3 = Try.fail(error1); 233 | Try failure4 = Try.fail(error1); 234 | 235 | assertEquals("Identical successful Trys should be equal", 236 | result2, result2); 237 | assertEquals("Same type and equal but distinct successful Trys should be equal", 238 | result2, result3); 239 | assertNotEquals("Same type but unequal successful Trys should not be equal", 240 | result3, result4); 241 | assertNotEquals("Differently-typed successful Trys should not be equal", 242 | result1, result2); 243 | 244 | assertEquals("Same type failed Trys with equal exceptions should be equal", 245 | failure1, failure4); 246 | assertEquals("Differently-typed failed Trys with equal exceptions should be equal", 247 | failure1, failure3); 248 | assertNotEquals("Same-typed failed Trys with different exceptions should not be equal", 249 | failure1, failure2); 250 | assertNotEquals("Differently-typed failed Trys with different exceptions should not be equal", 251 | failure2, failure3); 252 | } 253 | 254 | @Test 255 | public void testMapOnSuccessReturnsNewResult() { 256 | Try successful = Try.succeed(123); 257 | Try mapped = successful.map(Integer::toHexString); 258 | assertEquals( 259 | "map() on a successful Try should return the transformed result", 260 | Try.succeed("7b"), mapped); 261 | } 262 | 263 | @Test 264 | public void testMapOnSuccessWhichThrowsReturnsNewFailure() { 265 | ArithmeticException error = new ArithmeticException("Pretend we had a divide by zero"); 266 | Try successful = Try.succeed(123); 267 | Try mapped = successful.map((number) -> { 268 | if (true) { 269 | throw error; 270 | } else { 271 | return "this will never happen"; 272 | } 273 | }); 274 | assertEquals( 275 | "If the mapping function fails, the result should be a failure of the exception it threw", 276 | Try.fail(error), mapped); 277 | } 278 | 279 | @Test 280 | public void testMapOnFailureReturnsOldFailure() { 281 | Exception error = new IllegalStateException("Something went wrong"); 282 | Try failed = Try.fail(error); 283 | Try mapped = failed.map((number) -> { 284 | throw new ArithmeticException( 285 | "The map function should never be called for a failed Try"); 286 | }); 287 | assertEquals( 288 | "Mapping a failed Try should produce the same failed Try", 289 | failed, mapped); 290 | } 291 | 292 | @Test 293 | public void testFlatMapOnSuccessReturningSuccessReturnsNewResult() { 294 | Try successful = Try.succeed(123); 295 | Try flatMapped = successful 296 | .flatMap((number) -> Try.succeed(Integer.toHexString(number))); 297 | assertEquals( 298 | "flatMapping a successful Try should produce a new Try of the result", 299 | Try.succeed("7b"), flatMapped); 300 | } 301 | 302 | @Test 303 | public void testFlatMapOnSuccessReturningFailureReturnsNewFailure() { 304 | Exception error = new ArithmeticException("Someone tried to divide by zero"); 305 | Try successful = Try.succeed(123); 306 | Try flatMapped = successful 307 | .flatMap((number) -> Try.fail(error)); 308 | assertEquals( 309 | "flatMapping to a failure should produce that failure", 310 | Try.fail(error), flatMapped); 311 | } 312 | 313 | @Test 314 | public void testFlatMapOnSuccessWhichThrowsReturnsNewFailure() { 315 | IllegalStateException error = new IllegalStateException("Everything broke"); 316 | Try successful = Try.succeed(123); 317 | Try flatMapped = successful 318 | .flatMap((number) -> { 319 | if (true) { 320 | throw error; 321 | } else { 322 | return Try.succeed("this will never happen"); 323 | } 324 | }); 325 | assertEquals( 326 | "If the flatMapping function fails, the result should be a failure of the exception it threw", 327 | Try.fail(error), flatMapped); 328 | } 329 | } 330 | --------------------------------------------------------------------------------