1 /******************************************************************************
2 
3     Manages a set of parameters where each parameter is a string key/value pair.
4 
5     Wraps an associative array serving as map of parameter key and value
6     strings.
7     The parameter keys are set on instantiation; that is, a key list is passed
8     to the constructor. The keys cannot be changed, added or removed later by
9     ParamSet. However, a subclass can add keys.
10     All methods that accept a key handle the key case insensitively (except the
11     constructor). When keys are output, the original keys are used.
12     Note that keys and values are meant to slice string buffers in a subclass or
13     external to this class.
14 
15     Build note: Requires linking against libglib-2.0: add
16 
17     -L-lglib-2.0
18 
19     to the DMD build parameters.
20 
21     Copyright:
22         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
23         All rights reserved.
24 
25     License:
26         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
27         Alternatively, this file may be distributed under the terms of the Tango
28         3-Clause BSD License (see LICENSE_BSD.txt for details).
29 
30  ******************************************************************************/
31 
32 module ocean.net.util.ParamSet;
33 
34 
35 import ocean.meta.types.Qualifiers;
36 
37 import ocean.core.TypeConvert;
38 
39 import ocean.core.Verify;
40 
41 import ocean.text.util.SplitIterator: ISplitIterator;
42 import ocean.text.convert.Formatter;
43 
44 import core.stdc.ctype:  tolower;
45 
46 version (unittest) import ocean.core.Test;
47 
48 /******************************************************************************
49 
50     Compares each of the the first n characters in s1 and s2 in a
51     case-insensitive manner.
52 
53     @see http://www.gtk.org/api/2.6/glib/glib-String-Utility-Functions.html#g-ascii-strncasecmp
54 
55     Params:
56         s1 = string to compare each of the first n characters against those in s2
57         s2 = string to compare each of the first n characters against those in s1
58         n  = number of characters to compare
59 
60     Returns:
61         an integer less than, equal to, or greater than zero if the first n
62         characters of s1 is found, respectively, to be less than, to match, or
63         to be greater than the first n characters of s2
64 
65  ******************************************************************************/
66 
67 extern (C) private int g_ascii_strncasecmp ( const(char)* s1, const(char)* s2, size_t n );
68 
69 /******************************************************************************/
70 
71 class ParamSet
72 {
73     struct Element
74     {
75         cstring key, val;
76     }
77 
78     /**************************************************************************
79 
80         Set to true to skip key/value pairs with a null value on 'foreach'
81         iteration.
82 
83      **************************************************************************/
84 
85     public bool skip_null_values_on_iteration = false;
86 
87     /**************************************************************************
88 
89         Minimum required buffer length for decimal formatting of an uint value
90 
91      **************************************************************************/
92 
93     public static immutable ulong_dec_length = ulong.max.stringof.length;
94 
95     /**************************************************************************
96 
97         Key/value map of the parameter set
98 
99         Keys are the parameter keys in lower case, values are structs containing
100         the original key and the parameter value. The value stored in the struct
101         is set to null initially and by reset().
102 
103      **************************************************************************/
104 
105     private Element[istring] paramset;
106 
107     /**************************************************************************
108 
109         Reused buffer for case conversion
110 
111      **************************************************************************/
112 
113     private mstring tolower_buf;
114 
115     /**************************************************************************
116 
117         Obtains the parameter value corresponding to key. key must be one of
118         the parameter keys passed on instantiation or added by a subclass.
119 
120         Params:
121             key = parameter key (case insensitive)
122 
123         Returns:
124             parameter value; null indicates that no value is currently set for
125             this key
126 
127         Throws:
128             Behaves like regular associative array indexing.
129 
130      **************************************************************************/
131 
132     cstring opIndex ( cstring key )
133     {
134         key = this.tolower(key);
135         auto value = key in this.paramset;
136         verify(
137             value !is null,
138             format("Key '{}' is not found in ParamSet", key)
139         );
140 
141         return value.val;
142     }
143 
144     /**************************************************************************
145 
146         Obtains the parameter value corresponding to key.
147 
148         Params:
149             key = parameter key (case insensitive)
150 
151         Returns:
152             pointer to the corresponding parameter value or null if the key is
153             unknown. A pointer to null indicates that no value is currently set
154             for this key.
155 
156      **************************************************************************/
157 
158     cstring* opIn_r ( cstring key )
159     {
160         Element* element = this.get_(key);
161 
162         return element? &element.val : null;
163     }
164 
165     /**************************************************************************
166 
167         Obtains the parameter value corresponding to key, bundled with the
168         original key.
169 
170         Params:
171             key = parameter key (case insensitive)
172 
173         Returns:
174             Struct containing original key and parameter value or null for key
175             and value if the key was not found. A non-null key with a null value
176             indicates that no value is currently set for this key.
177 
178      **************************************************************************/
179 
180     Element getElement ( cstring key )
181     out (element)
182     {
183        assert (element.key || !element.val);
184     }
185     do
186     {
187         Element* element = this.get_(key);
188 
189         return element? *element : Element.init;
190     }
191 
192     /**************************************************************************
193 
194         Obtains the parameter value corresponding to key which is expected to be
195         an unsigned decimal integer number and not empty. key must be one of the
196         parameter keys passed on instantiation or added by a subclass.
197 
198         Params:
199             key    = parameter key (case insensitive)
200             n      = result destination; will be changed only if a value exists
201                      for key
202             is_set = will be changed to true if a value exists for key (even if
203                      it is empty or not an unsigned decimal integer number)
204 
205         Returns:
206             true on success or false if either no value exists for key or the
207             value is empty or not an unsigned decimal integer number or.
208 
209         Throws:
210             Behaves like regular associative array indexing using key as key.
211 
212      **************************************************************************/
213 
214     bool getUnsigned ( T : ulong ) ( cstring key, ref T n, out bool is_set )
215     {
216         cstring val = this[key];
217 
218         is_set = val !is null;
219 
220         return is_set? !this.readUnsigned(val, n).length && val.length : false;
221     }
222 
223     /**************************************************************************
224 
225         ditto
226 
227      **************************************************************************/
228 
229     bool getUnsigned ( T : ulong ) ( cstring key, ref T n )
230     {
231         cstring val = this[key];
232 
233         return val.length? !this.readUnsigned(val, n).length : false;
234     }
235 
236 
237     /**************************************************************************
238 
239         Sets the parameter value for key. Key must be one of the parameter keys
240         passed on instantiation or added by a subclass.
241 
242         Params:
243             val = parameter value (will be sliced)
244             key = parameter key (case insensitive)
245 
246         Returns:
247             val
248 
249         Throws:
250             Asserts that key is one of the parameter keys passed on
251             instantiation or added by a subclass.
252 
253      **************************************************************************/
254 
255     cstring opIndexAssign ( cstring val, cstring key )
256     {
257         Element* element = this.get_(key);
258 
259         verify(element !is null, "cannot assign to unknown key \""
260             ~ idup(key) ~ "\"");
261 
262         return element.val = val;
263     }
264 
265     /**************************************************************************
266 
267         Sets the parameter value for key if key is one of the parameter keys
268         passed on instantiation or added by a subclass.
269 
270         Params:
271             key = parameter key (case insensitive)
272             val = parameter value (will be sliced)
273 
274         Returns:
275             true if key is one of parameter keys passed on instantiation or
276             added by a subclass or false otherwise. In case of false nothing has
277             changed.
278 
279      **************************************************************************/
280 
281     bool set ( cstring key, cstring val )
282     {
283         return this.access(key, (cstring, ref cstring dst){dst = val;});
284     }
285 
286     /**************************************************************************
287 
288         ditto
289 
290         Params:
291             key = parameter key (case insensitive)
292             val = parameter value
293             dec = number to string conversion buffer, a slice will be associated
294                   as string value for key
295 
296         Returns:
297             true if key is one of parameter keys passed on instantiation or
298             false otherwise. In case of false nothing has changed.
299 
300      **************************************************************************/
301 
302     bool set ( cstring key, size_t val, mstring dec )
303     {
304         return this.access(key, (cstring, ref cstring dst)
305                                 {
306                                     dst = this.writeUnsigned(dec, val);
307                                 });
308     }
309 
310     /**************************************************************************
311 
312         Invokes dg with the original key and a reference to the parameter value
313         for key if key is one of parameter keys passed on instantiation or added
314         by a subclass.
315 
316         Params:
317             key = parameter key (case insensitive)
318             dg  = callback delegate
319 
320         Returns:
321             true if key is one of the parameter keys passed on instantiation or
322             added by a subclass or false otherwise. In case of false dg was not
323             invoked.
324 
325      **************************************************************************/
326 
327     bool access ( cstring key, scope void delegate ( cstring key, ref cstring val ) dg )
328     {
329         Element* element = this.get_(key);
330 
331         if (element)
332         {
333             dg(element.key, element.val);
334         }
335 
336         return element !is null;
337     }
338 
339     /**************************************************************************
340 
341         Compares the parameter value corresponding to key with val in a
342         case-insensitive manner.
343 
344         Params:
345             key = parameter key (case insensitive)
346             val = parameter value (case insensitive)
347 
348         Returns:
349             true if a parameter for key exists and its value case-insensitively
350             equals val.
351 
352      **************************************************************************/
353 
354     bool matches ( cstring key, cstring val )
355     {
356         Element* element = this.get_(key);
357 
358         return element?
359             (element.val.length == val.length) &&
360                 !this.strncasecmp(element.val, val) :
361             false;
362     }
363 
364     /**************************************************************************
365 
366         'foreach' iteration over parameter key/value pairs
367 
368      **************************************************************************/
369 
370     public int opApply ( scope int delegate ( ref cstring key, ref cstring val ) dg )
371     {
372         int result = 0;
373 
374         foreach (ref element; this.paramset)
375         {
376             this.iterate(element, dg, result);
377 
378             if (result) break;
379         }
380 
381         return result;
382     }
383 
384     /**************************************************************************
385 
386         Resets all parameter values to null.
387 
388      **************************************************************************/
389 
390     public void reset ( )
391     {
392         foreach (ref element; this.paramset)
393         {
394             element.val = null;
395         }
396     }
397 
398     /**************************************************************************
399 
400         Compares a to b, treating ASCII characters case-insensitively. If a and
401         b have a different length, the first common characters are compared. If
402         these are equal, the longer string compares greater.
403 
404         To see if the content of a and b is the same, use
405 
406         ---
407             (a.length == b.length) && !strncasecmp (a, b)
408         ---
409         .
410 
411         Treats null strings like empty strings.
412 
413         Params:
414             a = string to compare against b
415             b = string to compare against a
416 
417         Returns:
418             a value greater than 0 if a compares greater than b, less than 0
419             if a compares less than b, or 0 if a and b are of equal length and
420             all characters are equal.
421 
422      **************************************************************************/
423 
424     public static int strncasecmp ( cstring a, cstring b )
425     {
426         if ( a.length && b.length )
427         {
428             if ( a.length == b.length )
429             {
430                 return g_ascii_strncasecmp(a.ptr, b.ptr, a.length);
431             }
432             else
433             {
434                 bool a_is_shorter = a.length < b.length;
435                 int c = g_ascii_strncasecmp(a.ptr, b.ptr,
436                     a_is_shorter? a.length : b.length);
437                 return c? c : a_is_shorter? -1 : 1;
438             }
439         }
440         else
441         {
442             return (a.length < b.length)? -1 :
443                        (a.length > b.length)?  1 :
444                        0;
445         }
446     }
447 
448     unittest
449     {
450         test(strncasecmp("a", "b") < 0);
451         test(strncasecmp("b", "a") > 0);
452         test(strncasecmp("hello", "hello") == 0);
453         test(strncasecmp("hello", "Hello") == 0);
454         test(strncasecmp("hello", "HELLO") == 0);
455         test(strncasecmp("hello", "hello there") < 0);
456         test(strncasecmp("hello there", "hello") > 0);
457         test(strncasecmp("", "hell0") < 0);
458         test(strncasecmp("hello", "") > 0);
459         test(strncasecmp("", "") == 0);
460     }
461 
462     /**************************************************************************
463 
464         Adds an entry for key.
465 
466         Params:
467             keys = parameter key to add (will be duplicated)
468 
469      **************************************************************************/
470 
471     protected void addKeys ( in cstring[] keys ... )
472     {
473         foreach (key; keys)
474         {
475             this.addKey(key);
476         }
477     }
478 
479     /**************************************************************************
480 
481         Adds an entry for key.
482 
483         Params:
484             key = parameter key to add (will be duplicated)
485 
486      **************************************************************************/
487 
488     protected cstring addKey ( cstring key )
489     {
490         mstring lower_key = this.tolower(key);
491 
492         if (!(lower_key in this.paramset))
493         {
494             this.paramset[idup(lower_key)] = Element(key);
495         }
496 
497         return lower_key;
498     }
499 
500     /**************************************************************************
501 
502         Looks up key in a case-insensitive manner.
503 
504         Params:
505             key = parameter key
506 
507         Returns:
508             - Pointer to a a struct which contains the original key and the
509               parameter value, where a null value indicates that no value is
510               currently set for this key, or
511             - null if the key was not found.
512 
513      **************************************************************************/
514 
515     protected Element* get_ ( cstring key )
516     out (element)
517     {
518         if (element)
519             assert (element.key || !element.val);
520     }
521     do
522     {
523         return this.tolower(key) in this.paramset;
524     }
525 
526     /**************************************************************************
527 
528         Converts key to lower case, writing to a separate buffer so that key is
529         left untouched.
530 
531         Params:
532             key = key to convert to lower case
533 
534         Returns:
535             result (references an internal buffer)
536 
537      **************************************************************************/
538 
539     protected mstring tolower ( cstring key )
540     {
541         if (this.tolower_buf.length < key.length)
542         {
543             this.tolower_buf.length = key.length;
544         }
545 
546         foreach (i, c; key)
547         {
548             this.tolower_buf[i] = castFrom!(int).to!(char)(.tolower(c));
549         }
550 
551         return this.tolower_buf[0 .. key.length];
552     }
553 
554     /**************************************************************************
555 
556         Rehashes the associative array.
557 
558      **************************************************************************/
559 
560     protected void rehash ( )
561     {
562         this.paramset.rehash;
563     }
564 
565     /**************************************************************************
566 
567         opApply() helper, invokes dg with element.key & val.
568 
569         Params:
570             element = element currently iterating over
571             dg      = opApply() iteration delegate
572             result  = set to dg() return value if dg is invoked, remains
573                       unchanged otherwise.
574 
575      **************************************************************************/
576 
577     final protected void iterate ( ref Element element,
578                                    scope int delegate ( ref cstring key, ref cstring val ) dg,
579                                    ref int result )
580     {
581         with (element) if (val || !this.skip_null_values_on_iteration)
582         {
583             result = dg(key, val);
584         }
585     }
586 
587     /**************************************************************************
588 
589         Converts n to decimal representation, writing to dst. dst must be long
590         enough to hold the result. The result will be written to the end of dst,
591         returning a slice to the valid content in dst.
592 
593         Params:
594             dst = destination string
595             n   = number to convert to decimal representation
596 
597         Returns:
598             result (dst)
599 
600      **************************************************************************/
601 
602     protected static mstring writeUnsigned ( mstring dst, ulong n )
603     out (dec)
604     {
605         assert (!n);
606         assert (&dec[$ - 1] is &dst[$ - 1]);
607     }
608     do
609     {
610         foreach_reverse (i, ref c; dst)
611         {
612             ulong quot = n / 10;
613 
614             c = castFrom!(ulong).to!(char)(n - (quot * 10) + '0');
615             n = quot;
616 
617             if (!n)
618             {
619                 return dst[i .. $];
620             }
621         }
622 
623         assert(false);
624     }
625 
626     unittest
627     {
628         char[ulong_dec_length] dec;
629 
630         test (writeUnsigned(dec, 4711)     == "4711");
631         test (writeUnsigned(dec, 0)        == "0");
632 
633         test (writeUnsigned(dec, uint.max) == "4294967295");
634         test (writeUnsigned(dec, ulong.max) == "18446744073709551615");
635 
636         test (strncasecmp("", "a") < 0);
637 
638     }
639 
640     /**************************************************************************
641 
642         Converts str, which is expected to contain a decimal number, to the
643         number it represents. Tailing and leading whitespace is allowed and will
644         be trimmed. If src contains non-decimal digit characters after trimming,
645         conversion will be stopped at the first non-decimal digit character.
646 
647         Example:
648 
649         ---
650 
651             uint n;
652 
653             cstring remaining = readUnsigned("  123abc45  ", n);
654 
655             // n is now 123
656             // remaining is now "abc45"
657 
658         ---
659 
660         Params:
661             src = source string
662             x   = result output
663 
664         Returns:
665             slice of src starting with the first character that is not a decimal
666             digit or an empty string if src contains only decimal digits
667 
668      **************************************************************************/
669 
670     protected static cstring readUnsigned ( T : ulong ) ( cstring src, out T x )
671     in
672     {
673         static assert (T.init == 0, "initial value of type \"" ~ T.stringof ~ "\" is " ~ T.init.stringof ~ " (need 0)");
674         static assert (cast (T) (T.max + 1) < T.max);                           // ensure overflow checking works
675     }
676     do
677     {
678         cstring trimmed = ISplitIterator.trim(src);
679 
680         foreach (i, c; trimmed)
681         {
682             if ('0' <= c && c <= '9')
683             {
684                 T y = x * 10 + (c - '0');
685 
686                 if (y >= x)                                                     // overflow checking
687                 {
688                     x = y;
689                     continue;
690                 }
691             }
692 
693             return trimmed[i .. $];
694         }
695 
696         return src? src[$ .. $] : null;
697     }
698 }