View Javadoc
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(&quot;Car may not be null.&quot;);
19   * }
20   * if (Double.isNaN(car.getPosition()))
21   * {
22   *     throw new IllegalArgumentException(&quot;Position of car &quot; + car + &quot; is NaN.&quot;);
23   * }
24   * </pre>
25   * 
26   * we can write:
27   * 
28   * <pre>
29   * Throw.whenNull(car, &quot;Car may not be null.&quot;);
30   * Throw.when(Double.isNaN(car.getPosition()), IllegalArgumentException.class, &quot;Position of car %s is NaN.&quot;, 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, &quot;Value may not be NaN.&quot;);
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&lt;T&gt;; 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, &quot;Value may not be NaN for object %s.&quot;, 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&lt;T&gt;; 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      *         &quot;Value may not be NaN for object %s with name %s.&quot;, 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&lt;T&gt;; 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      *         &quot;Value may not be NaN for object %s with name %s and id %s.&quot;, 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&lt;T&gt;; 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      *         &quot;Value may not be NaN for object %s with name %s, id %s and parent %s.&quot;, 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&lt;T&gt;; 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&lt;T&gt;; the Throwable type to throw
200      * @param message String; the message to use in the exception, with potential formatting identifiers
201      * @param argList List&lt;Object&gt;; 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&lt;T&gt;; 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      *         &quot;Value may not be NaN for object %s.&quot;, 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&lt;T&gt;; 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      *         &quot;Value may not be NaN for object %s with name %s.&quot;, 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&lt;T&gt;; 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      *         &quot;Value may not be NaN for object %s with name %s and id %s.&quot;, 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&lt;T&gt;; 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      *         &quot;Value may not be NaN for object %s with name %s, id %s and parent %s.&quot;, 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&lt;T&gt;; 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(), &quot;Value may not be null.&quot;);
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(), &quot;Value may not be null for object %s.&quot;, 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(), &quot;Value may not be null for object %s with name %s.&quot;, 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(), &quot;Value may not be null for object %s with name %s and id %s.&quot;, 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(), &quot;Value may not be null for object %s with name %s, id %s and parent %s.&quot;, 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      *   &#64;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 }