-
Notifications
You must be signed in to change notification settings - Fork 2
/
Try.java
301 lines (281 loc) · 9.92 KB
/
Try.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
package com.kirekov.juu.monad;
import com.kirekov.juu.collection.Streaming;
import com.kirekov.juu.lambda.CheckedFunction;
import com.kirekov.juu.lambda.CheckedSupplier;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
/**
* Monad for retrieving values just like {@link Optional}, but instead a container considered as an
* empty one if an exception has been thrown during calculations.
* <br>
* The monad acts <b>lazily</b>. So, all methods build a pipeline which execution is triggered on
* any terminal operation.
* <br>
* List of terminal operations.
* <ul>
* <li>{@linkplain Try#orElse(Object)}</li>
* <li>{@linkplain Try#orElseGet(Supplier)}</li>
* <li>{@linkplain Try#orElseGet(Function)}</li>
* <li>{@linkplain Try#orElseThrow()}</li>
* <li>{@linkplain Try#orElseThrow(Supplier)}</li>
* <li>{@linkplain Try#stream()}</li>
* </ul>
* <p>The class only catches exceptions of type {@link Exception}. It means that all {@linkplain
* Throwable} instances shall be skipped. The motivation is that {@link Error} extends from
* {@linkplain Throwable} but this exceptions should not be caught manually.</p>
* <br>
* The class is thread-safe if the pipeline is thread-safe too.
*
* @param <T> the type of the return value
* @since 1.0
*/
public final class Try<T> implements Streaming<T> {
private final CheckedSupplier<? extends T, ? extends Exception> valueSupplier;
private Try(CheckedSupplier<? extends T, ? extends Exception> value) {
this.valueSupplier = value;
}
/**
* Create a monad with successful result.
*
* @param value the value to retrieve
* @param <T> the type of the value
* @return monad with successful execution
*/
public static <T> Try<T> success(T value) {
return new Try<>(() -> value);
}
/**
* Create a monad with error result. The execution throws {@linkplain NoSuchElementException}.
*
* @param <T> the type of the return value
* @return monad with error result
*/
public static <T> Try<T> error() {
return new Try<>(() -> {
throw new NoSuchElementException("'Try' container is empty");
});
}
/**
* Create a monad with error result. The execution throws {@code exceptionToThrow}.
*
* @param <T> the type of the return value
* @param exceptionToThrow the exception that is ought to be thrown
* @return monad with error result
* @throws NullPointerException if {@code exceptionToThrow} is null
*/
public static <T> Try<T> error(Exception exceptionToThrow) {
Objects.requireNonNull(exceptionToThrow, "exceptionToThrow cannot be null");
return new Try<>(() -> {
throw exceptionToThrow;
});
}
/**
* Create a monad of the given supplier. This is an intermediate operation.
*
* @param supplier supplier that returns value
* @param <T> the type of the return value
* @return monad that holds that given supplier
* @throws NullPointerException if suppliers parameter is null
*/
public static <T> Try<T> of(CheckedSupplier<? extends T, ? extends Exception> supplier) {
Objects.requireNonNull(supplier, "supplier cannot be null");
return new Try<>(supplier);
}
/**
* Map the value from one to another and return new monad. This is an intermediate operation.
*
* @param mapper mapping function
* @param <U> the type of the new value
* @return a monad with mapped function
* @throws NullPointerException if {@code mapper} is null
*/
public <U> Try<U> map(CheckedFunction<? super T, ? extends U, ? extends Exception> mapper) {
Objects.requireNonNull(mapper, "mapper cannot be null");
return Try.of(() -> mapper.apply(valueSupplier.get()));
}
/**
* Map the value from one to another and returns new monad. The mapping function must return
* another {@link Try} container. This is an intermediate operation.
* <br>
* For instance,
* <pre>{@code
* Try.of(() -> 1)
* .flatMap(v -> Try.of(() -> v + 1))
* .orElse(-1)
* }</pre>
* returns 2, while
* <pre>{@code
* Try.of(() -> 1)
* .flatMap(v -> Try.of(() -> v / 0))
* .orElse(-1)
* }</pre>
* returns -1
*
* @param mapper mapping function
* @param <U> the type of the new value
* @return monad with new value or an empty one
* @throws NullPointerException if {@code mapper} is null
* @see Try#map(CheckedFunction)
*/
public <U> Try<U> flatMap(
CheckedFunction<? super T, ? extends Try<? extends U>, ? extends Exception> mapper) {
Objects.requireNonNull(mapper, "mapper cannot be null");
return Try.of(() -> mapper.apply(valueSupplier.get()).orElseThrow());
}
/**
* Filter the value with the given {@code predicate}. If {@code predicate} returns {@code false},
* the {@linkplain NoSuchElementException} is thrown. This is an intermediate operation.
*
* @param predicate predicate function
* @return the container itself or an empty one
* @throws NullPointerException if predicate is null
*/
public Try<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate, "predicate cannot be null");
return Try.of(() -> {
final T value = valueSupplier.get();
if (predicate.test(value)) {
return value;
}
throw new NoSuchElementException("The filter does not pass");
});
}
/**
* Calculate the value and return the new monad that holds it. Otherwise, return the monad of the
* default value. This is an intermediate operation.
* <br>
* <pre>{@code
* Try.of(() -> "str")
* .orElseTry(() -> "newStr")
* .orElse("null")
* }</pre>
* returns {@code "str"}.
* <pre>{@code
* Try.<String>of(() -> {throw new Exception();})
* .orElseTry(() -> "newStr")
* .orElse("null")
* }</pre>
* returns {@code "newStr"}
* <pre>{@code
* Try.<String>of(() -> {throw new Exception();})
* .orElseTry(() -> {throw new Exception();})
* .orElse("null")
* }</pre>
* returns {@code "null"}.
*
* @param defaultValueSupplier supplier that provides the default value
* @return the monad of the calculated value or the default one
* @throws NullPointerException if {@code defaultValueSupplier} is null
*/
public Try<T> orElseTry(CheckedSupplier<? extends T, ? extends Exception> defaultValueSupplier) {
Objects.requireNonNull(defaultValueSupplier, "defaultValueSupplier cannot be null");
return new Try<>(() -> {
try {
return valueSupplier.get();
} catch (Exception e) {
return defaultValueSupplier.get();
}
});
}
/**
* Calculate the value and return it, if the calculation does not fail. Otherwise, return the
* default value. This is a terminal operation.
*
* @param other the default value
* @return the calculated value or the default one
*/
public T orElse(T other) {
try {
return valueSupplier.get();
} catch (Exception e) {
return other;
}
}
/**
* Calculate the value and return it, if the calculation does not fail. Otherwise, return the
* defaultValue. This is a terminal operation.
*
* @param defaultValueSupplier supplier the provides the default value
* @return the calculated value or the default one
* @throws NullPointerException if {@code defaultValueSupplier} is null
*/
public T orElseGet(Supplier<? extends T> defaultValueSupplier) {
Objects.requireNonNull(defaultValueSupplier, "defaultValueSupplier cannot be null");
try {
return valueSupplier.get();
} catch (Exception e) {
return defaultValueSupplier.get();
}
}
/**
* Calculate the value and return it, if the calculation does not fail. Otherwise, return the
* default value. This is a terminal operation.
*
* @param defaultValueFunction function that accepts the exception that occurred and returns the
* default value
* @return the calculated value or the default one
* @throws NullPointerException if {@code defaultValueFunction} is null
*/
public T orElseGet(Function<? super Exception, ? extends T> defaultValueFunction) {
Objects.requireNonNull(defaultValueFunction, "defaultValueFunction cannot be null");
try {
return valueSupplier.get();
} catch (Exception e) {
return defaultValueFunction.apply(e);
}
}
/**
* Calculate the value and return it, if the calculation does not fail. Otherwise, throws the
* exception that led to error.
*
* @return the value of the container
*/
public T orElseThrow() {
try {
return valueSupplier.get();
} catch (Exception e) {
return throwException(e);
}
}
/**
* Calculate the value and return it, if the calculation does not fail. Otherwise, throw the
* supplied exception. This is a terminal operation.
*
* @param exceptionSupplier supplier, that returns exception
* @param <E> the type of the exception
* @return the value of the container
* @throws E if the value calculation fails
* @throws NullPointerException if {@code exceptionSupplier} is null
*/
public <E extends Exception> T orElseThrow(Supplier<? extends E> exceptionSupplier) throws E {
Objects.requireNonNull(exceptionSupplier);
try {
return valueSupplier.get();
} catch (Exception e) {
throw exceptionSupplier.get();
}
}
/**
* Transform a monad to {@linkplain Stream}. This is a terminal operation.
*
* @return stream of container's value, if the calculation passes. Otherwise, returns {@link
* Stream#empty()}
*/
@Override
public Stream<T> stream() {
try {
return Stream.of(valueSupplier.get());
} catch (Exception e) {
return Stream.empty();
}
}
@SuppressWarnings("unchecked")
private static <E extends Exception, T> T throwException(Exception exception) throws E {
throw (E) exception;
}
}