1 package org.djutils.exceptions;
2
3 import java.lang.reflect.Constructor;
4 import java.util.ArrayList;
5 import java.util.Arrays;
6 import java.util.IllegalFormatException;
7 import java.util.List;
8
9 import org.djutils.reflection.ClassUtil;
10
11 /**
12 * The Try class has a number of static methods that make it easy to try-catch an exception for any Throwable class, including
13 * the standard Java exceptions and exceptions from libraries that are used in the project. Instead of:
14 *
15 * <pre>
16 * FileInputStream fis;
17 * try
18 * {
19 * fis = new FileInputStream(fileString);
20 * }
21 * catch (FileNotFoundException exception)
22 * {
23 * throw new IllegalArgumentException("File " + fileString + " is not a valid file.", exception);
24 * }
25 * try
26 * {
27 * fis.close();
28 * }
29 * catch (IOException exception)
30 * {
31 * throw new RuntimeException("Could not close the file.", exception);
32 * }
33 * </pre>
34 *
35 * we can write:
36 *
37 * <pre>
38 * FileInputStream fis = Try.assign(() -> new FileInputStream(fileString), IllegalArgumentException.class,
39 * "File %s is not a valid file.", fileString);
40 * Try.execute(() -> fis.close(), "Could not close the file.");
41 * </pre>
42 *
43 * The exception message can be formatted with additional arguments, such that the overhead of building the exception message
44 * only occurs if the exception condition is met. For each method there is a version without Throwable class, in which case a
45 * RuntimeException will be thrown.<br>
46 * <br>
47 * Try is not suitable for try-with-resource statements.<br>
48 * <br>
49 * Try also has a few methods to aid JUNIT tests: {@code testFail(...)} and {@code testNotFail(...)}.
50 * <p>
51 * Copyright (c) 2016-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
52 * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
53 * distributed under a three-clause BSD-style license, which can be found at
54 * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>.
55 * </p>
56 * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
57 * @author <a href="https://www.tudelft.nl/staff/p.knoppers/">Peter Knoppers</a>
58 * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
59 */
60 public final class Try
61 {
62 /** private constructor for utility class. */
63 private Try()
64 {
65 // utility class
66 }
67
68 // Assign
69
70 /**
71 * Tries to return a value to assign. Will throw a RuntimeException if the try fails.
72 * @param assignment functional interface to assign value
73 * @param message the message to use in the throwable
74 * @param <V> value type
75 * @return value to assign
76 * @throws RuntimeException on failed Try
77 */
78 public static <V> V assign(final Assignment<V> assignment, final String message) throws RuntimeException
79 {
80 try
81 {
82 return assignment.assign();
83 }
84 catch (Throwable cause)
85 {
86 throw catchThrowable(RuntimeException.class, message, new ArrayList<>(), cause);
87 }
88 }
89
90 /**
91 * Tries to return a value to assign. Will throw a RuntimeException if the try fails.
92 * @param assignment functional interface to assign value
93 * @param message the message to use in the throwable, with formatting identifier
94 * @param arg value to use for the formatting identifier
95 * @param <V> value type
96 * @return value to assign
97 * @throws RuntimeException on failed Try
98 */
99 public static <V> V assign(final Assignment<V> assignment, final String message, final Object arg) throws RuntimeException
100 {
101 try
102 {
103 return assignment.assign();
104 }
105 catch (Throwable cause)
106 {
107 List<Object> argList = new ArrayList<>();
108 argList.add(arg);
109 throw catchThrowable(RuntimeException.class, message, argList, cause);
110 }
111 }
112
113 /**
114 * Tries to return a value to assign. Will throw a RuntimeException if the try fails.
115 * @param assignment functional interface to assign value
116 * @param message the message to use in the throwable, with formatting identifiers
117 * @param arg1 1st value to use for the formatting identifiers
118 * @param arg2 2nd value to use for the formatting identifiers
119 * @param <V> value type
120 * @return value to assign
121 * @throws RuntimeException on failed Try
122 */
123 public static <V> V assign(final Assignment<V> assignment, final String message, final Object arg1, final Object arg2)
124 throws RuntimeException
125 {
126 try
127 {
128 return assignment.assign();
129 }
130 catch (Throwable cause)
131 {
132 List<Object> argList = new ArrayList<>();
133 argList.add(arg1);
134 argList.add(arg2);
135 throw catchThrowable(RuntimeException.class, message, argList, cause);
136 }
137 }
138
139 /**
140 * Tries to return a value to assign. Will throw a RuntimeException if the try fails.
141 * @param assignment functional interface to assign value
142 * @param message the message to use in the throwable, with formatting identifiers
143 * @param arg1 1st value to use for the formatting identifiers
144 * @param arg2 2nd value to use for the formatting identifiers
145 * @param arg3 3rd value to use for the formatting identifiers
146 * @param <V> value type
147 * @return value to assign
148 * @throws RuntimeException on failed Try
149 */
150 public static <V> V assign(final Assignment<V> assignment, final String message, final Object arg1, final Object arg2,
151 final Object arg3) throws RuntimeException
152 {
153 try
154 {
155 return assignment.assign();
156 }
157 catch (Throwable cause)
158 {
159 List<Object> argList = new ArrayList<>();
160 argList.add(arg1);
161 argList.add(arg2);
162 argList.add(arg3);
163 throw catchThrowable(RuntimeException.class, message, argList, cause);
164 }
165 }
166
167 /**
168 * Tries to return a value to assign. Will throw a RuntimeException if the try fails.
169 * @param assignment functional interface to assign value
170 * @param message the message to use in the throwable, with formatting identifiers
171 * @param arg1 1st value to use for the formatting identifiers
172 * @param arg2 2nd value to use for the formatting identifiers
173 * @param arg3 3rd value to use for the formatting identifiers
174 * @param args potential 4th and further values to use for the formatting identifiers
175 * @param <V> value type
176 * @return value to assign
177 * @throws RuntimeException on failed Try
178 */
179 public static <V> V assign(final Assignment<V> assignment, final String message, final Object arg1, final Object arg2,
180 final Object arg3, final Object... args) throws RuntimeException
181 {
182 try
183 {
184 return assignment.assign();
185 }
186 catch (Throwable cause)
187 {
188 List<Object> argList = new ArrayList<>();
189 argList.add(arg1);
190 argList.add(arg2);
191 argList.add(arg3);
192 argList.addAll(Arrays.asList(args));
193 throw catchThrowable(RuntimeException.class, message, argList, cause);
194 }
195 }
196
197 /**
198 * Tries to return a value to assign. Will throw a specified Throwable if the try fails.
199 * @param assignment functional interface to assign value
200 * @param throwableClass class of the throwable to throw
201 * @param message the message to use in the throwable
202 * @param <V> value type
203 * @param <T> throwable type
204 * @return value to assign
205 * @throws T throwable on failed Try
206 */
207 public static <V, T extends Throwable> V assign(final Assignment<V> assignment, final Class<T> throwableClass,
208 final String message) throws T
209 {
210 try
211 {
212 return assignment.assign();
213 }
214 catch (Throwable cause)
215 {
216 throw catchThrowable(throwableClass, message, new ArrayList<>(), cause);
217 }
218 }
219
220 /**
221 * Tries to return a value to assign. Will throw a specified Throwable if the try fails.
222 * @param assignment functional interface to assign value
223 * @param throwableClass class of the throwable to throw
224 * @param message the message to use in the throwable, with formatting identifier
225 * @param arg value to use for the formatting identifier
226 * @param <V> value type
227 * @param <T> throwable type
228 * @return value to assign
229 * @throws T throwable on failed Try
230 */
231 public static <V, T extends Throwable> V assign(final Assignment<V> assignment, final Class<T> throwableClass,
232 final String message, final Object arg) throws T
233 {
234 try
235 {
236 return assignment.assign();
237 }
238 catch (Throwable cause)
239 {
240 List<Object> argList = new ArrayList<>();
241 argList.add(arg);
242 throw catchThrowable(throwableClass, message, argList, cause);
243 }
244 }
245
246 /**
247 * Tries to return a value to assign. Will throw a specified Throwable if the try fails.
248 * @param assignment functional interface to assign value
249 * @param throwableClass class of the throwable to throw
250 * @param message the message to use in the throwable, with formatting identifiers
251 * @param arg1 1st value to use for the formatting identifiers
252 * @param arg2 2nd value to use for the formatting identifiers
253 * @param <V> value type
254 * @param <T> throwable type
255 * @return value to assign
256 * @throws T throwable on failed Try
257 */
258 public static <V, T extends Throwable> V assign(final Assignment<V> assignment, final Class<T> throwableClass,
259 final String message, final Object arg1, final Object arg2) throws T
260 {
261 try
262 {
263 return assignment.assign();
264 }
265 catch (Throwable cause)
266 {
267 List<Object> argList = new ArrayList<>();
268 argList.add(arg1);
269 argList.add(arg2);
270 throw catchThrowable(throwableClass, message, argList, cause);
271 }
272 }
273
274 /**
275 * Tries to return a value to assign. Will throw a specified Throwable if the try fails.
276 * @param assignment functional interface to assign value
277 * @param throwableClass class of the throwable to throw
278 * @param message the message to use in the throwable, with formatting identifiers
279 * @param arg1 1st value to use for the formatting identifiers
280 * @param arg2 2nd value to use for the formatting identifiers
281 * @param arg3 3rd value to use for the formatting identifiers
282 * @param <V> value type
283 * @param <T> throwable type
284 * @return value to assign
285 * @throws T throwable on failed Try
286 */
287 public static <V, T extends Throwable> V assign(final Assignment<V> assignment, final Class<T> throwableClass,
288 final String message, final Object arg1, final Object arg2, final Object arg3) throws T
289 {
290 try
291 {
292 return assignment.assign();
293 }
294 catch (Throwable cause)
295 {
296 List<Object> argList = new ArrayList<>();
297 argList.add(arg1);
298 argList.add(arg2);
299 argList.add(arg3);
300 throw catchThrowable(throwableClass, message, argList, cause);
301 }
302 }
303
304 /**
305 * Tries to return a value to assign. Will throw a specified Throwable if the try fails.
306 * @param assignment functional interface to assign value
307 * @param throwableClass class of the throwable to throw
308 * @param message the message to use in the throwable, with formatting identifiers
309 * @param arg1 1st value to use for the formatting identifiers
310 * @param arg2 2nd value to use for the formatting identifiers
311 * @param arg3 3rd value to use for the formatting identifiers
312 * @param args potential 4th and further values to use for the formatting identifiers
313 * @param <V> value type
314 * @param <T> throwable type
315 * @return value to assign
316 * @throws T throwable on failed Try
317 */
318 public static <V, T extends Throwable> V assign(final Assignment<V> assignment, final Class<T> throwableClass,
319 final String message, final Object arg1, final Object arg2, final Object arg3, final Object... args) throws T
320 {
321 try
322 {
323 return assignment.assign();
324 }
325 catch (Throwable cause)
326 {
327 List<Object> argList = new ArrayList<>();
328 argList.add(arg1);
329 argList.add(arg2);
330 argList.add(arg3);
331 argList.addAll(Arrays.asList(args));
332 throw catchThrowable(throwableClass, message, argList, cause);
333 }
334 }
335
336 // Execute
337
338 /**
339 * Tries to execute. Will throw a RuntimeException if the try fails.
340 * @param execution functional interface to execute
341 * @param message the message to use in the throwable
342 * @throws RuntimeException on failed Try
343 */
344 public static void execute(final Execution execution, final String message) throws RuntimeException
345 {
346 try
347 {
348 execution.execute();
349 }
350 catch (Throwable cause)
351 {
352 throw catchThrowable(RuntimeException.class, message, new ArrayList<>(), cause);
353 }
354 }
355
356 /**
357 * Tries to execute. Will throw a RuntimeException if the try fails.
358 * @param execution functional interface to execute
359 * @param message the message to use in the throwable, with formatting identifier
360 * @param arg value to use for the formatting identifier
361 * @throws RuntimeException on failed Try
362 */
363 public static void execute(final Execution execution, final String message, final Object arg) throws RuntimeException
364 {
365 try
366 {
367 execution.execute();
368 }
369 catch (Throwable cause)
370 {
371 List<Object> argList = new ArrayList<>();
372 argList.add(arg);
373 throw catchThrowable(RuntimeException.class, message, argList, cause);
374 }
375 }
376
377 /**
378 * Tries to execute. Will throw a RuntimeException if the try fails.
379 * @param execution functional interface to execute
380 * @param message the message to use in the throwable, with formatting identifiers
381 * @param arg1 1st value to use for the formatting identifiers
382 * @param arg2 2nd value to use for the formatting identifiers
383 * @throws RuntimeException on failed Try
384 */
385 public static void execute(final Execution execution, final String message, final Object arg1, final Object arg2)
386 throws RuntimeException
387 {
388 try
389 {
390 execution.execute();
391 }
392 catch (Throwable cause)
393 {
394 List<Object> argList = new ArrayList<>();
395 argList.add(arg1);
396 argList.add(arg2);
397 throw catchThrowable(RuntimeException.class, message, argList, cause);
398 }
399 }
400
401 /**
402 * Tries to execute. Will throw a RuntimeException if the try fails.
403 * @param execution functional interface to execute
404 * @param message the message to use in the throwable, with formatting identifiers
405 * @param arg1 1st value to use for the formatting identifiers
406 * @param arg2 2nd value to use for the formatting identifiers
407 * @param arg3 3rd value to use for the formatting identifiers
408 * @throws RuntimeException on failed Try
409 */
410 public static void execute(final Execution execution, final String message, final Object arg1, final Object arg2,
411 final Object arg3) throws RuntimeException
412 {
413 try
414 {
415 execution.execute();
416 }
417 catch (Throwable cause)
418 {
419 List<Object> argList = new ArrayList<>();
420 argList.add(arg1);
421 argList.add(arg2);
422 argList.add(arg3);
423 throw catchThrowable(RuntimeException.class, message, argList, cause);
424 }
425 }
426
427 /**
428 * Tries to execute. Will throw a RuntimeException if the try fails.
429 * @param execution functional interface to execute
430 * @param message the message to use in the throwable, with formatting identifiers
431 * @param arg1 1st value to use for the formatting identifiers
432 * @param arg2 2nd value to use for the formatting identifiers
433 * @param arg3 3rd value to use for the formatting identifiers
434 * @param args potential 4th and further values to use for the formatting identifiers
435 * @throws RuntimeException on failed Try
436 */
437 public static void execute(final Execution execution, final String message, final Object arg1, final Object arg2,
438 final Object arg3, final Object... args) throws RuntimeException
439 {
440 try
441 {
442 execution.execute();
443 }
444 catch (Throwable cause)
445 {
446 List<Object> argList = new ArrayList<>();
447 argList.add(arg1);
448 argList.add(arg2);
449 argList.add(arg3);
450 argList.addAll(Arrays.asList(args));
451 throw catchThrowable(RuntimeException.class, message, argList, cause);
452 }
453 }
454
455 /**
456 * Tries to execute. Will throw a specified Throwable if the try fails.
457 * @param execution functional interface to execute
458 * @param throwableClass class of the throwable to throw
459 * @param message the message to use in the throwable
460 * @param <T> throwable type
461 * @throws T throwable on failed Try
462 */
463 public static <T extends Throwable> void execute(final Execution execution, final Class<T> throwableClass,
464 final String message) throws T
465 {
466 try
467 {
468 execution.execute();
469 }
470 catch (Throwable cause)
471 {
472 throw catchThrowable(throwableClass, message, new ArrayList<>(), cause);
473 }
474 }
475
476 /**
477 * Tries to execute. Will throw a specified Throwable if the try fails.
478 * @param execution functional interface to execute
479 * @param throwableClass class of the throwable to throw
480 * @param message the message to use in the throwable, with formatting identifier
481 * @param arg value to use for the formatting identifier
482 * @param <T> throwable type
483 * @throws T throwable on failed Try
484 */
485 public static <T extends Throwable> void execute(final Execution execution, final Class<T> throwableClass,
486 final String message, final Object arg) throws T
487 {
488 try
489 {
490 execution.execute();
491 }
492 catch (Throwable cause)
493 {
494 List<Object> argList = new ArrayList<>();
495 argList.add(arg);
496 throw catchThrowable(throwableClass, message, argList, cause);
497 }
498 }
499
500 /**
501 * Tries to execute. Will throw a specified Throwable if the try fails.
502 * @param execution functional interface to execute
503 * @param throwableClass class of the throwable to throw
504 * @param message the message to use in the throwable, with formatting identifiers
505 * @param arg1 1st value to use for the formatting identifiers
506 * @param arg2 2nd value to use for the formatting identifiers
507 * @param <T> throwable type
508 * @throws T throwable on failed Try
509 */
510 public static <T extends Throwable> void execute(final Execution execution, final Class<T> throwableClass,
511 final String message, final Object arg1, final Object arg2) throws T
512 {
513 try
514 {
515 execution.execute();
516 }
517 catch (Throwable cause)
518 {
519 List<Object> argList = new ArrayList<>();
520 argList.add(arg1);
521 argList.add(arg2);
522 throw catchThrowable(throwableClass, message, argList, cause);
523 }
524 }
525
526 /**
527 * Tries to execute. Will throw a specified Throwable if the try fails.
528 * @param execution functional interface to execute
529 * @param throwableClass class of the throwable to throw
530 * @param message the message to use in the throwable, with formatting identifiers
531 * @param arg1 1st value to use for the formatting identifiers
532 * @param arg2 2nd value to use for the formatting identifiers
533 * @param arg3 3rd value to use for the formatting identifiers
534 * @param <T> throwable type
535 * @throws T throwable on failed Try
536 */
537 public static <T extends Throwable> void execute(final Execution execution, final Class<T> throwableClass,
538 final String message, final Object arg1, final Object arg2, final Object arg3) throws T
539 {
540 try
541 {
542 execution.execute();
543 }
544 catch (Throwable cause)
545 {
546 List<Object> argList = new ArrayList<>();
547 argList.add(arg1);
548 argList.add(arg2);
549 argList.add(arg3);
550 throw catchThrowable(throwableClass, message, argList, cause);
551 }
552 }
553
554 /**
555 * Tries to execute. Will throw a specified Throwable if the try fails.
556 * @param execution functional interface to execute
557 * @param throwableClass class of the throwable to throw
558 * @param message the message to use in the throwable, with formatting identifiers
559 * @param arg1 1st value to use for the formatting identifiers
560 * @param arg2 2nd value to use for the formatting identifiers
561 * @param arg3 3rd value to use for the formatting identifiers
562 * @param args potential 4th and further values to use for the formatting identifiers
563 * @param <T> throwable type
564 * @throws T throwable on failed Try
565 */
566 public static <T extends Throwable> void execute(final Execution execution, final Class<T> throwableClass,
567 final String message, final Object arg1, final Object arg2, final Object arg3, final Object... args) throws T
568 {
569 try
570 {
571 execution.execute();
572 }
573 catch (Throwable cause)
574 {
575 List<Object> argList = new ArrayList<>();
576 argList.add(arg1);
577 argList.add(arg2);
578 argList.add(arg3);
579 argList.addAll(Arrays.asList(args));
580 throw catchThrowable(throwableClass, message, argList, cause);
581 }
582 }
583
584 // Core of assign/execute methods
585
586 /**
587 * Core method to create the Throwable to throw.
588 * @param throwableClass the throwable class
589 * @param message the message to construct when an exception is thrown.
590 * @param argList List<Object> the arguments as implied by format escapes in <code>message</code>
591 * @param cause underlying cause thrown inside the assign()/execute()
592 * @param <T> throwable type
593 * @return throwable
594 */
595 private static <T extends Throwable> T catchThrowable(final Class<T> throwableClass, final String message,
596 final List<Object> argList, final Throwable cause)
597 {
598 // create a clear message
599 List<StackTraceElement> steList = new ArrayList<>(Arrays.asList(new Throwable().getStackTrace()));
600 // see https://stackoverflow.com/questions/2411487/nullpointerexception-in-java-with-no-stacktrace
601 // and https://hg.openjdk.java.net/jdk/jdk/file/tip/src/hotspot/share/opto/graphKit.cpp
602 if (steList.size() > 2)
603 {
604 steList.remove(0); // remove the catchThrowable(...) call
605 steList.remove(0); // remove the Try.assign/execute(...) call
606 }
607 StackTraceElement[] ste = steList.toArray(new StackTraceElement[steList.size()]);
608 String where = ste[0].getClassName() + "." + ste[0].getMethodName() + " (" + ste[0].getLineNumber() + "): ";
609 Object[] args = argList.toArray();
610 String formattedMessage;
611 try
612 {
613 formattedMessage = where + String.format(message, args);
614 }
615 catch (IllegalFormatException exception)
616 {
617 formattedMessage = where + message + " [FormatException; args=" + argList + "]";
618 }
619
620 // throw all other exceptions through reflection
621 T exception;
622 try
623 {
624 Constructor<T> constructor =
625 ClassUtil.resolveConstructor(throwableClass, new Class<?>[] {String.class, Throwable.class});
626 List<StackTraceElement> steCause = new ArrayList<>(Arrays.asList(cause.getStackTrace()));
627 // see https://stackoverflow.com/questions/2411487/nullpointerexception-in-java-with-no-stacktrace
628 // and https://hg.openjdk.java.net/jdk/jdk/file/tip/src/hotspot/share/opto/graphKit.cpp
629 if (steCause.size() > 3)
630 {
631 steCause.remove(steCause.size() - 1); // remove method that called Try.assign/execute(...)
632 steCause.remove(steCause.size() - 1); // remove the Try.assign/execute(...) call
633 steCause.remove(steCause.size() - 1); // remove the Assignment/Execution implementation (can be lambda$#)
634 cause.setStackTrace(steCause.toArray(new StackTraceElement[steCause.size()]));
635 }
636 exception = constructor.newInstance(formattedMessage, cause);
637 exception.setStackTrace(ste);
638 }
639 catch (Throwable t)
640 {
641 RuntimeException rte = new RuntimeException(t.getMessage(), new Exception(formattedMessage, cause));
642 rte.setStackTrace(ste);
643 throw rte;
644 }
645 return exception;
646 }
647
648 // Interfaces
649
650 /**
651 * Functional interface for calls to Try.assign(...). For this a lambda expression can be used.
652 *
653 * <pre>
654 * FileInputStream fis = Try.assign(() -> new FileInputStream(fileString), IllegalArgumentException.class,
655 * "File %s is not a valid file.", fileString);
656 * </pre>
657 * <p>
658 * Copyright (c) 2013-2025 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
659 * <br>
660 * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
661 * </p>
662 * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
663 * @author <a href="https://www.tudelft.nl/staff/p.knoppers/">Peter Knoppers</a>
664 * @param <V> value type
665 */
666 @FunctionalInterface
667 public interface Assignment<V>
668 {
669 /**
670 * Returns a value which is obtained from the context in which the Assignment was created.
671 * @return value which is obtained from the context in which the Assignment was created
672 * @throws Throwable on any throwable in the try
673 */
674 V assign() throws Throwable;
675 }
676
677 /**
678 * Functional interface for calls to Try.execute(...). For this a lambda expression can be used.
679 *
680 * <pre>
681 * Try.execute(() -> fis.close(), "Could not close the file.");
682 * </pre>
683 * <p>
684 * Copyright (c) 2013-2025 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
685 * <br>
686 * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
687 * </p>
688 * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
689 * @author <a href="https://www.tudelft.nl/staff/p.knoppers/">Peter Knoppers</a>
690 */
691 @FunctionalInterface
692 public interface Execution
693 {
694 /**
695 * Executes some code using the context in which the Execution was created.
696 * @throws Throwable on any throwable in the try
697 */
698 void execute() throws Throwable;
699 }
700
701 }