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 Throw class has a number of static methods that make it easy to throw an exception under conditions for any Exception
13 * class, including the standard Java exceptions and exceptions from libraries that are used in the project. Instead of:
14 *
15 * <pre>
16 * if (car == null)
17 * {
18 * throw new NullPointerException("Car may not be null.");
19 * }
20 * if (Double.isNaN(car.getPosition()))
21 * {
22 * throw new IllegalArgumentException("Position of car " + car + " is NaN.");
23 * }
24 * </pre>
25 *
26 * we can write:
27 *
28 * <pre>
29 * Throw.whenNull(car, "Car may not be null.");
30 * Throw.when(Double.isNaN(car.getPosition()), IllegalArgumentException.class, "Position of car %s is NaN.", car);
31 * </pre>
32 *
33 * The exception message can be formatted with additional arguments, such that the overhead of building the exception message
34 * only occurs if the exception condition is met. All methods have a version where the first parameter is returned. Thereby, the
35 * Throw can be used as part of a <b>super</b>(...) call in a constructor.
36 * <p>
37 * Copyright (c) 2016-2023 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
38 * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
39 * distributed under a three-clause BSD-style license, which can be found at
40 * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>.
41 * </p>
42 * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
43 * @author <a href="https://www.tudelft.nl/staff/p.knoppers/">Peter Knoppers</a>
44 * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
45 */
46 @SuppressWarnings("checkstyle:linelength")
47 public final class Throw
48 {
49 /** private constructor for utility class. */
50 private Throw()
51 {
52 // utility class
53 }
54
55 /**
56 * Throw a Throwable (such as an Exception or Error) if a condition is met, e.g. for pre- and postcondition checking. Use as
57 * follows: <br>
58 *
59 * <pre>
60 * Throw.when(Double.isNan(object.getValue()), IllegalArgumentException.class, "Value may not be NaN.");
61 * </pre>
62 *
63 * @param condition boolean; the condition to check; an exception will be thrown if this is <b>true</b>
64 * @param throwableClass Class<T>; the Throwable type to throw
65 * @param message String; the message to use in the exception
66 * @throws T the throwable to throw on true condition
67 * @param <T> the Throwable type
68 */
69 public static <T extends Throwable> void when(final boolean condition, final Class<T> throwableClass, final String message)
70 throws T
71 {
72 if (condition)
73 {
74 throwMessage(throwableClass, message, new ArrayList<>());
75 }
76 }
77
78 /**
79 * Throw a Throwable (such as an Exception or Error) if a condition is met, e.g. for pre- and postcondition checking. Use as
80 * follows: <br>
81 *
82 * <pre>
83 * Throw.when(Double.isNan(object.getValue()), IllegalArgumentException.class, "Value may not be NaN for object %s.", object);
84 * </pre>
85 *
86 * @param condition boolean; the condition to check; an exception will be thrown if this is <b>true</b>
87 * @param throwableClass Class<T>; the Throwable type to throw
88 * @param message String; the message to use in the exception, with formatting identifiers
89 * @param arg Object; value to use for the formatting identifiers
90 * @throws T the throwable to throw on true condition
91 * @param <T> the Throwable type
92 */
93 public static <T extends Throwable> void when(final boolean condition, final Class<T> throwableClass, final String message,
94 final Object arg) throws T
95 {
96 if (condition)
97 {
98 List<Object> argList = new ArrayList<>();
99 argList.add(arg);
100 throwMessage(throwableClass, message, argList);
101 }
102 }
103
104 /**
105 * Throw a Throwable (such as an Exception or Error) if a condition is met, e.g. for pre- and postcondition checking. Use as
106 * follows: <br>
107 *
108 * <pre>
109 * Throw.when(Double.isNan(object.getValue()), IllegalArgumentException.class,
110 * "Value may not be NaN for object %s with name %s.", object, name);
111 * </pre>
112 *
113 * @param condition boolean; the condition to check; an exception will be thrown if this is <b>true</b>
114 * @param throwableClass Class<T>; the Throwable type to throw
115 * @param message String; the message to use in the exception, with formatting identifiers
116 * @param arg1 Object; 1st value to use for the formatting identifiers
117 * @param arg2 Object; 2nd value to use for the formatting identifiers
118 * @throws T the throwable to throw on true condition
119 * @param <T> the Throwable type
120 */
121 public static <T extends Throwable> void when(final boolean condition, final Class<T> throwableClass, final String message,
122 final Object arg1, final Object arg2) throws T
123 {
124 if (condition)
125 {
126 List<Object> argList = new ArrayList<>();
127 argList.add(arg1);
128 argList.add(arg2);
129 throwMessage(throwableClass, message, argList);
130 }
131 }
132
133 /**
134 * Throw a Throwable (such as an Exception or Error) if a condition is met, e.g. for pre- and postcondition checking. Use as
135 * follows: <br>
136 *
137 * <pre>
138 * Throw.when(Double.isNan(object.getValue()), IllegalArgumentException.class,
139 * "Value may not be NaN for object %s with name %s and id %s.", object, name, id);
140 * </pre>
141 *
142 * @param condition boolean; the condition to check; an exception will be thrown if this is <b>true</b>
143 * @param throwableClass Class<T>; the Throwable type to throw
144 * @param message String; the message to use in the exception, with formatting identifiers
145 * @param arg1 Object; 1st value to use for the formatting identifiers
146 * @param arg2 Object; 2nd value to use for the formatting identifiers
147 * @param arg3 Object; 3rd value to use for the formatting identifiers
148 * @throws T the throwable to throw on true condition
149 * @param <T> the Throwable type
150 */
151 public static <T extends Throwable> void when(final boolean condition, final Class<T> throwableClass, final String message,
152 final Object arg1, final Object arg2, final Object arg3) throws T
153 {
154 if (condition)
155 {
156 List<Object> argList = new ArrayList<>();
157 argList.add(arg1);
158 argList.add(arg2);
159 argList.add(arg3);
160 throwMessage(throwableClass, message, argList);
161 }
162 }
163
164 /**
165 * Throw a Throwable (such as an Exception or Error) if a condition is met, e.g. for pre- and postcondition checking. Use as
166 * follows: <br>
167 *
168 * <pre>
169 * Throw.when(Double.isNan(object.getValue()), IllegalArgumentException.class,
170 * "Value may not be NaN for object %s with name %s, id %s and parent %s.", object, name, id, parent);
171 * </pre>
172 *
173 * @param condition boolean; the condition to check; an exception will be thrown if this is <b>true</b>
174 * @param throwableClass Class<T>; the Throwable type to throw
175 * @param message String; the message to use in the exception, with formatting identifiers
176 * @param arg1 Object; 1st value to use for the formatting identifiers
177 * @param arg2 Object; 2nd value to use for the formatting identifiers
178 * @param arg3 Object; 3rd value to use for the formatting identifiers
179 * @param args Object...; potential 4th and further values to use for the formatting identifiers
180 * @throws T the throwable to throw on true condition
181 * @param <T> the Throwable type
182 */
183 public static <T extends Throwable> void when(final boolean condition, final Class<T> throwableClass, final String message,
184 final Object arg1, final Object arg2, final Object arg3, final Object... args) throws T
185 {
186 if (condition)
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 throwMessage(throwableClass, message, argList);
194 }
195 }
196
197 /**
198 * Private method to handle the throwing an Exception, Throwable or Error.
199 * @param throwableClass Class<T>; the Throwable type to throw
200 * @param message String; the message to use in the exception, with potential formatting identifiers
201 * @param argList List<Object>; List with potential values to use for the formatting identifiers
202 * @throws T the throwable to throw
203 * @param <T> the Throwable type
204 */
205 private static <T extends Throwable> void throwMessage(final Class<T> throwableClass, final String message,
206 final List<Object> argList) throws T
207 {
208 // create a clear message
209 List<StackTraceElement> steList = new ArrayList<>(Arrays.asList(new Throwable().getStackTrace()));
210 steList.remove(0); // remove the throwMessage(...) call
211 steList.remove(0); // remove the when(...) call
212 StackTraceElement[] ste = steList.toArray(new StackTraceElement[steList.size()]);
213 String where = ste[0].getClassName() + "." + ste[0].getMethodName() + " (" + ste[0].getLineNumber() + "): ";
214 Object[] args = argList.toArray();
215 String formattedMessage;
216 try
217 {
218 formattedMessage = where + String.format(message, args);
219 }
220 catch (IllegalFormatException exception)
221 {
222 formattedMessage = where + message + " [FormatException; args=" + argList + "]";
223 }
224
225 // throw all other exceptions through reflection
226 T exception;
227 try
228 {
229 Constructor<T> constructor = ClassUtil.resolveConstructor(throwableClass, new Class<?>[] {String.class});
230 exception = constructor.newInstance(formattedMessage);
231 exception.setStackTrace(ste);
232 }
233 catch (Throwable t)
234 {
235 RuntimeException rte = new RuntimeException(t.getMessage(), new Exception(formattedMessage));
236 rte.setStackTrace(ste);
237 throw rte;
238 }
239 throw exception;
240 }
241
242 /**
243 * Throw a Throwable (such as an Exception or Error) if a condition is met, e.g. for pre- and postcondition checking. This
244 * version of the method returns its first parameter, so it can be used inside a constructor. Use e.g., as follows:
245 *
246 * <pre>
247 * super(Throw.when(object, Double.isNaN(object.getValue()), IllegalArgumentException.class, "Value may not be NaN."));
248 * </pre>
249 *
250 * @param object O; the object to return by this static method
251 * @param condition boolean; the condition to check; an exception will be thrown if this is <b>true</b>
252 * @param throwableClass Class<T>; the Throwable type to throw
253 * @param message String; the message to use in the exception
254 * @throws T the throwable to throw on true condition
255 * @param <T> the Throwable type
256 * @param <O> the Object type to return
257 * @return the object that was passed as the first parameter
258 */
259 public static <T extends Throwable, O extends Object> O when(final O object, final boolean condition,
260 final Class<T> throwableClass, final String message) throws T
261 {
262 if (condition)
263 {
264 throwMessage(throwableClass, message, new ArrayList<>());
265 }
266 return object;
267 }
268
269 /**
270 * Throw a Throwable (such as an Exception or Error) if a condition is met, e.g. for pre- and postcondition checking. This
271 * version of the method returns its first parameter, so it can be used inside a constructor. Use e.g., as follows:
272 *
273 * <pre>
274 * super(Throw.when(object, Double.isNan(object.getValue()), IllegalArgumentException.class,
275 * "Value may not be NaN for object %s.", object));
276 * </pre>
277 *
278 * @param object O; the object to return by this static method
279 * @param condition boolean; the condition to check; an exception will be thrown if this is <b>true</b>
280 * @param throwableClass Class<T>; the Throwable type to throw
281 * @param message String; the message to use in the exception, with formatting identifiers
282 * @param arg Object; value to use for the formatting identifiers
283 * @throws T the throwable to throw on true condition
284 * @param <T> the Throwable type
285 * @param <O> the Object type to return
286 * @return the object that was passed as the first parameter
287 */
288 public static <T extends Throwable, O extends Object> O when(final O object, final boolean condition,
289 final Class<T> throwableClass, final String message, final Object arg) throws T
290 {
291 if (condition)
292 {
293 List<Object> argList = new ArrayList<>();
294 argList.add(arg);
295 throwMessage(throwableClass, message, argList);
296 }
297 return object;
298 }
299
300 /**
301 * Throw a Throwable (such as an Exception or Error) if a condition is met, e.g. for pre- and postcondition checking. This
302 * version of the method returns its first parameter, so it can be used inside a constructor. Use e.g., as follows:
303 *
304 * <pre>
305 * super(Throw.when(object, Double.isNan(object.getValue()), IllegalArgumentException.class,
306 * "Value may not be NaN for object %s with name %s.", object, name));
307 * </pre>
308 *
309 * @param object O; the object to return by this static method
310 * @param condition boolean; the condition to check; an exception will be thrown if this is <b>true</b>
311 * @param throwableClass Class<T>; the Throwable type to throw
312 * @param message String; the message to use in the exception, with formatting identifiers
313 * @param arg1 Object; 1st value to use for the formatting identifiers
314 * @param arg2 Object; 2nd value to use for the formatting identifiers
315 * @throws T the throwable to throw on true condition
316 * @param <T> the Throwable type
317 * @param <O> the Object type to return
318 * @return the object that was passed as the first parameter
319 */
320 public static <T extends Throwable, O extends Object> O when(final O object, final boolean condition,
321 final Class<T> throwableClass, final String message, final Object arg1, final Object arg2) throws T
322 {
323 if (condition)
324 {
325 List<Object> argList = new ArrayList<>();
326 argList.add(arg1);
327 argList.add(arg2);
328 throwMessage(throwableClass, message, argList);
329 }
330 return object;
331 }
332
333 /**
334 * Throw a Throwable (such as an Exception or Error) if a condition is met, e.g. for pre- and postcondition checking. This
335 * version of the method returns its first parameter, so it can be used inside a constructor. Use e.g., as follows:
336 *
337 * <pre>
338 * super(Throw.when(object, Double.isNan(object.getValue()), IllegalArgumentException.class,
339 * "Value may not be NaN for object %s with name %s and id %s.", object, name, id));
340 * </pre>
341 *
342 * @param object O; the object to return by this static method
343 * @param condition boolean; the condition to check; an exception will be thrown if this is <b>true</b>
344 * @param throwableClass Class<T>; the Throwable type to throw
345 * @param message String; the message to use in the exception, with formatting identifiers
346 * @param arg1 Object; 1st value to use for the formatting identifiers
347 * @param arg2 Object; 2nd value to use for the formatting identifiers
348 * @param arg3 Object; 3rd value to use for the formatting identifiers
349 * @throws T the throwable to throw on true condition
350 * @param <T> the Throwable type
351 * @param <O> the Object type to return
352 * @return the object that was passed as the first parameter
353 */
354 public static <T extends Throwable, O extends Object> O when(final O object, final boolean condition,
355 final Class<T> throwableClass, final String message, final Object arg1, final Object arg2, final Object arg3)
356 throws T
357 {
358 if (condition)
359 {
360 List<Object> argList = new ArrayList<>();
361 argList.add(arg1);
362 argList.add(arg2);
363 argList.add(arg3);
364 throwMessage(throwableClass, message, argList);
365 }
366 return object;
367 }
368
369 /**
370 * Throw a Throwable (such as an Exception or Error) if a condition is met, e.g. for pre- and postcondition checking. This
371 * version of the method returns its first parameter, so it can be used inside a constructor. Use e.g., as follows:
372 *
373 * <pre>
374 * super(Throw.when(object, Double.isNan(object.getValue()), IllegalArgumentException.class,
375 * "Value may not be NaN for object %s with name %s, id %s and parent %s.", object, name, id, parent));
376 * </pre>
377 *
378 * @param object O; the object to return by this static method
379 * @param condition boolean; the condition to check; an exception will be thrown if this is <b>true</b>
380 * @param throwableClass Class<T>; the Throwable type to throw
381 * @param message String; the message to use in the exception, with formatting identifiers
382 * @param arg1 Object; 1st value to use for the formatting identifiers
383 * @param arg2 Object; 2nd value to use for the formatting identifiers
384 * @param arg3 Object; 3rd value to use for the formatting identifiers
385 * @param args Object...; potential 4th and further values to use for the formatting identifiers
386 * @throws T the throwable to throw on true condition
387 * @param <T> the Throwable type
388 * @param <O> the Object type to return
389 * @return the object that was passed as the first parameter
390 */
391 @SuppressWarnings("checkstyle:parameternumber")
392 public static <T extends Throwable, O extends Object> O when(final O object, final boolean condition,
393 final Class<T> throwableClass, final String message, final Object arg1, final Object arg2, final Object arg3,
394 final Object... args) throws T
395 {
396 if (condition)
397 {
398 List<Object> argList = new ArrayList<>();
399 argList.add(arg1);
400 argList.add(arg2);
401 argList.add(arg3);
402 argList.addAll(Arrays.asList(args));
403 throwMessage(throwableClass, message, argList);
404 }
405 return object;
406 }
407
408 /**
409 * Throw a NullPointerException if object is null, e.g. for pre- and postcondition checking. Use as follows: <br>
410 *
411 * <pre>
412 * Throw.when(object.getValue(), "Value may not be null.");
413 * </pre>
414 *
415 * @param object object to check; an exception will be thrown if this is <b>null</b>
416 * @param message String; the message to use in the exception
417 * @param <O> the Object type to return
418 * @return the object that was passed as the first parameter
419 * @throws NullPointerException if object is null
420 */
421 public static <O extends Object> O whenNull(final O object, final String message) throws NullPointerException
422 {
423 if (object == null)
424 {
425 throwMessage(NullPointerException.class, message, new ArrayList<>());
426 }
427 return object;
428 }
429
430 /**
431 * Throw a NullPointerException if object is null, e.g. for pre- and postcondition checking. Use as follows: <br>
432 *
433 * <pre>
434 * Throw.whenNull(object.getValue(), "Value may not be null for object %s.", object);
435 * </pre>
436 *
437 * @param object object to check; an exception will be thrown if this is <b>null</b>
438 * @param message String; the message to use in the exception, with formatting identifiers
439 * @param arg Object; value to use for the formatting identifiers
440 * @param <O> the Object type to return
441 * @return the object that was passed as the first parameter
442 * @throws NullPointerException if object is null
443 */
444 public static <O extends Object> O whenNull(final O object, final String message, final Object arg)
445 throws NullPointerException
446 {
447 if (object == null)
448 {
449 List<Object> argList = new ArrayList<>();
450 argList.add(arg);
451 throwMessage(NullPointerException.class, message, argList);
452 }
453 return object;
454 }
455
456 /**
457 * Throw a NullPointerException if object is null, e.g. for pre- and postcondition checking. Use as follows: <br>
458 *
459 * <pre>
460 * Throw.whenNull(object.getValue(), "Value may not be null for object %s with name %s.", object, name);
461 * </pre>
462 *
463 * @param object object to check; an exception will be thrown if this is <b>null</b>
464 * @param message String; the message to use in the exception, with formatting identifiers
465 * @param arg1 Object; 1st value to use for the formatting identifiers
466 * @param arg2 Object; 2nd value to use for the formatting identifiers
467 * @param <O> the Object type to return
468 * @return the object that was passed as the first parameter
469 * @throws NullPointerException if object is null
470 */
471 public static <O extends Object> O whenNull(final O object, final String message, final Object arg1, final Object arg2)
472 throws NullPointerException
473 {
474 if (object == null)
475 {
476 List<Object> argList = new ArrayList<>();
477 argList.add(arg1);
478 argList.add(arg2);
479 throwMessage(NullPointerException.class, message, argList);
480 }
481 return object;
482 }
483
484 /**
485 * Throw a NullPointerException if object is null, e.g. for pre- and postcondition checking. Use as follows: <br>
486 *
487 * <pre>
488 * Throw.whenNull(object.getValue(), "Value may not be null for object %s with name %s and id %s.", object, name, id);
489 * </pre>
490 *
491 * @param object object to check; an exception will be thrown if this is <b>null</b>
492 * @param message String; the message to use in the exception, with formatting identifiers
493 * @param arg1 Object; 1st value to use for the formatting identifiers
494 * @param arg2 Object; 2nd value to use for the formatting identifiers
495 * @param arg3 Object; 3rd value to use for the formatting identifiers
496 * @param <O> the Object type to return
497 * @return the object that was passed as the first parameter
498 * @throws NullPointerException if object is null
499 */
500 public static <O extends Object> O whenNull(final O object, final String message, final Object arg1, final Object arg2,
501 final Object arg3) throws NullPointerException
502 {
503 if (object == null)
504 {
505 List<Object> argList = new ArrayList<>();
506 argList.add(arg1);
507 argList.add(arg2);
508 argList.add(arg3);
509 throwMessage(NullPointerException.class, message, argList);
510 }
511 return object;
512 }
513
514 /**
515 * Throw a NullPointerException if object is null, e.g. for pre- and postcondition checking. Use as follows: <br>
516 *
517 * <pre>
518 * Throw.whenNull(object.getValue(), "Value may not be null for object %s with name %s, id %s and parent %s.", object, name, id,
519 * parent);
520 * </pre>
521 *
522 * @param object object to check; an exception will be thrown if this is <b>null</b>
523 * @param message String; the message to use in the exception, with formatting identifiers
524 * @param arg1 Object; 1st value to use for the formatting identifiers
525 * @param arg2 Object; 2nd value to use for the formatting identifiers
526 * @param arg3 Object; 3rd value to use for the formatting identifiers
527 * @param args Object...; potential 4th and further values to use for the formatting identifiers
528 * @param <O> the Object type to return
529 * @return the object that was passed as the first parameter
530 * @throws NullPointerException if object is null
531 */
532 public static <O extends Object> O whenNull(final O object, final String message, final Object arg1, final Object arg2,
533 final Object arg3, final Object... args) throws NullPointerException
534 {
535 if (object == null)
536 {
537 List<Object> argList = new ArrayList<>();
538 argList.add(arg1);
539 argList.add(arg2);
540 argList.add(arg3);
541 argList.addAll(Arrays.asList(args));
542 throwMessage(NullPointerException.class, message, argList);
543 }
544 return object;
545 }
546
547 /**
548 * Throw an unchecked exception for a method with a fixed signature (e.g., extending a method from a library that cannot be
549 * changed), without having to declare the exception, which can be impossible when extending a method. The typical use is:
550 *
551 * <pre>
552 * @Override
553 * public void someMethod() {
554 * try {
555 * // some code that throws e.g., an IOException
556 * } catch IOException e {
557 * Throw.throwUnchecked(e);
558 * }
559 * }
560 * </pre>
561 *
562 * From: <a href="http://blog.ragozin.info/2011/10/java-how-to-throw-undeclared-checked.html" target="_blank">
563 * http://blog.ragozin.info/2011/10/java-how-to-throw-undeclared-checked.html</a> as mentioned in <a href=
564 * "https://stackoverflow.com/questions/11942946/how-to-throw-an-exception-when-your-method-signature-doesnt-allow-to-throw-exce"
565 * target="_blank">
566 * https://stackoverflow.com/questions/11942946/how-to-throw-an-exception-when-your-method-signature-doesnt-allow-to-throw-exce</a>.
567 * @param e Throwavble; the exception of Throwable to throw in an unchecked manner
568 */
569 public static void throwUnchecked(final Throwable e)
570 {
571 Throw.<RuntimeException>throwAny(e);
572 }
573
574 /**
575 * @param <E> The exception class
576 * @param e Throwable; The throwable
577 * @throws E The exception to throw
578 */
579 @SuppressWarnings("unchecked")
580 private static <E extends Throwable> void throwAny(final Throwable e) throws E
581 {
582 throw (E) e;
583 }
584 }