1 /******************************************************************************
2 
3     Ocean Exceptions
4 
5     Copyright:
6         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
7         All rights reserved.
8 
9     License:
10         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
11         Alternatively, this file may be distributed under the terms of the Tango
12         3-Clause BSD License (see LICENSE_BSD.txt for details).
13 
14 *******************************************************************************/
15 
16 module ocean.core.Exception;
17 
18 
19 import ocean.transition;
20 
21 static import ocean.core.Enforce;
22 
23 version (UnitTest)
24 {
25     import ocean.core.Test;
26 }
27 
28 
29 /******************************************************************************
30 
31     Creates an iteratable data structure over a chained sequence of
32     exceptions.
33 
34 *******************************************************************************/
35 
36 struct ExceptionChain
37 {
38     /**************************************************************************
39 
40         Exception that forms the root of the exception chain.  This can
41         be passed in like a constructor argument:
42 
43             foreach (e; ExceptionChain(myException))
44             {
45                 ...
46             }
47 
48     ***************************************************************************/
49 
50     private Throwable root;
51 
52 
53     /**************************************************************************
54 
55         Allows the user to iterate over the exception chain
56 
57     ***************************************************************************/
58 
59     public int opApply (scope int delegate (ref Throwable) dg)
60     {
61         int result;
62 
63         for (auto e = root; e !is null; e = e.next)
64         {
65             result = dg(e);
66 
67             if (result)
68             {
69                 break;
70             }
71         }
72 
73         return result;
74     }
75 }
76 
77 unittest
78 {
79     auto e1 = new Exception("1");
80     auto e2 = new Exception("2", __FILE__, __LINE__, e1);
81 
82     size_t counter;
83     foreach (e; ExceptionChain(e2))
84         ++counter;
85     test (counter == 2);
86 }
87 
88 /******************************************************************************
89 
90     Common code to implement reusable exception semantics - one that has
91     mutable message buffer where message data gets stored to
92 
93 ******************************************************************************/
94 
95 public template ReusableExceptionImplementation()
96 {
97     import ocean.transition;
98     import ocean.core.Buffer;
99     static import ocean.core.array.Mutation;
100     static import ocean.text.convert.Formatter;
101     static import ocean.text.convert.Integer_tango;
102 
103     static assert (is(typeof(this) : Exception));
104 
105     /**************************************************************************
106 
107         Fields used instead of `msg` for mutable messages. `Exception.msg`
108         has `istring` type thus can't be overwritten with new data
109 
110     ***************************************************************************/
111 
112     protected Buffer!(char) reused_msg;
113 
114     /***************************************************************************
115 
116         Constructs exception object with mutable buffer pre-allocated to length
117         `size` and other fields kept invalid.
118 
119         Params:
120             size = initial size/length of mutable message buffer
121 
122     ***************************************************************************/
123 
124     this (size_t size = 128)
125     {
126         this.reused_msg.length = size;
127         super(null);
128     }
129 
130     /***************************************************************************
131 
132         Sets exception information for this instance.
133 
134         Params:
135             msg  = exception message
136             file = file where the exception has been thrown
137             line = line where the exception has been thrown
138 
139         Returns:
140             this instance
141 
142     ***************************************************************************/
143 
144     public typeof (this) set ( cstring msg, istring file = __FILE__,
145         long line = __LINE__ )
146     {
147         ocean.core.array.Mutation.copy(this.reused_msg, msg);
148         this.msg  = null;
149         this.file = file;
150         this.line = line;
151         return this;
152     }
153 
154    /***************************************************************************
155 
156         Throws this instance if ok is false, 0 or null.
157 
158         Params:
159             ok   = condition to enforce
160             msg  = exception message
161             file = file where the exception has been thrown
162             line = line where the exception has been thrown
163 
164         Throws:
165             this instance if ok is false, 0 or null.
166 
167     ***************************************************************************/
168 
169     public void enforce ( T ) ( T ok, lazy cstring msg, istring file = __FILE__,
170         long line = __LINE__ )
171     {
172         if (!ok)
173             throw this.set(msg, file, line);
174     }
175 
176     static if (is(typeof(Throwable.message)))
177     {
178         /**************************************************************************
179 
180             Returns:
181                 currently active exception message
182 
183         ***************************************************************************/
184 
185         public override cstring message ( ) const
186         {
187             return this.msg[].ptr is null ? this.reused_msg[] : this.msg;
188         }
189     }
190 
191     /**************************************************************************
192 
193         Appends new substring to mutable exception message
194 
195         Intended to be used in hosts that do dynamic formatting of the
196         error message and want to avoid repeating allocation with help
197         of ReusableException semantics
198 
199         Leaves other fields untouched
200 
201         Params:
202             msg = string to append to the message
203 
204         Returns:
205             this instance
206 
207     ***************************************************************************/
208 
209     public typeof (this) append ( cstring msg )
210     {
211         ocean.core.array.Mutation.append(this.reused_msg, msg);
212         return this;
213     }
214 
215     /**************************************************************************
216 
217         Appends an integer to mutable exception message
218 
219         Does not cause any appenditional allocations
220 
221         Params:
222             num = number to be formatted
223             hex = optional, indicates that value needs to be formatted as hex
224 
225         Returns:
226             this instance
227 
228     **************************************************************************/
229 
230     public typeof (this) append ( long num, bool hex = false )
231     {
232         char[long.max.stringof.length + 1] buff;
233         if (hex)
234         {
235             ocean.core.array.Mutation.append(this.reused_msg, "0x"[]);
236             ocean.core.array.Mutation.append(
237                 this.reused_msg,
238                 ocean.text.convert.Integer_tango.format(buff, num, "X"));
239         }
240         else
241             ocean.core.array.Mutation.append(
242                 this.reused_msg,
243                 ocean.text.convert.Integer_tango.format(buff, num));
244         return this;
245     }
246 
247     /**************************************************************************
248 
249         Appends formatted string
250 
251         Params:
252             fmt = string format to use
253             args = variadic args list to format
254 
255         Returns:
256             this instance
257 
258     **************************************************************************/
259 
260     public typeof (this) fmtAppend ( Args... ) ( cstring fmt, Args args )
261     {
262         ocean.text.convert.Formatter.sformat(this.reused_msg, fmt, args);
263         return this;
264     }
265 }
266 
267 ///
268 unittest
269 {
270     auto e = new SomeReusableException(100);
271 
272     e.set("message");
273     test (e.message() == "message");
274     auto old_ptr = e.reused_msg[].ptr;
275 
276     try
277     {
278         ocean.core.Enforce.enforce(e, false, "immutable");
279         assert (false);
280     }
281     catch (SomeReusableException) { }
282     test (e.message() == "immutable");
283 
284     try
285     {
286         e.enforce(false, "longer message");
287     }
288     catch (SomeReusableException) { }
289     test (e.message() == "longer message");
290     test (old_ptr is e.reused_msg[].ptr);
291 
292     try
293     {
294         e.badName("NAME", 42);
295     }
296     catch (SomeReusableException) { }
297     test (e.message() == "Wrong name (NAME) 0x2A 42");
298     test (old_ptr is e.reused_msg[].ptr);
299 }
300 
301 unittest
302 {
303     auto e = new SomeReusableException;
304     e.set("aaa").fmtAppend("{0}{0}", 42);
305     test!("==")(e.message(), "aaa4242");
306 }
307 
308 version (UnitTest)
309 {
310     private class SomeReusableException : Exception
311     {
312         void badName(istring name, uint id)
313         {
314             this.set("Wrong name (")
315                 .append(name)
316                 .append(") ")
317                 .append(id, true)
318                 .append(" ")
319                 .append(id);
320             throw this;
321         }
322 
323         mixin ReusableExceptionImplementation!();
324     }
325 }
326 
327 
328 /******************************************************************************
329 
330     Common code to implement exception constructor, which takes a message as
331     a required parameter, and file and line with default value, and forward
332     it to the `super` constructor
333 
334 ******************************************************************************/
335 
336 public template DefaultExceptionCtor()
337 {
338     import ocean.transition;
339 
340     public this (istring msg, istring file = __FILE__,
341                  typeof(__LINE__) line = __LINE__)
342     {
343         super (msg, file, line);
344     }
345 }
346 
347 version (UnitTest)
348 {
349     public class CardBoardException : Exception
350     {
351         mixin DefaultExceptionCtor;
352     }
353 }
354 
355 ///
356 unittest
357 {
358     auto e = new CardBoardException("Transmogrification failed");
359     try
360     {
361         throw e;
362     }
363     catch (CardBoardException e)
364     {
365         test!("==")(e.message(), "Transmogrification failed"[]);
366         test!("==")(e.file, __FILE__[]);
367         test!("==")(e.line, __LINE__ - 9);
368     }
369 }
370 
371 // Check that the inherited `Exception.toString` is not
372 // shadowed by anything (like nested imports).
373 unittest
374 {
375     auto s = (new SomeReusableException).toString();
376 }