* documented hashlist
[citadel.git] / libcitadel / lib / hash.c
1 #include <stdint.h>
2 #include <stdlib.h>
3 #include <string.h>
4 //dbg
5 #include <stdio.h>
6 #include "libcitadel.h"
7 #include "lookup3.h"
8
9 typedef struct Payload Payload;
10
11 struct Payload {
12         /**
13          * \brief Hash Payload storage Structure; filled in linear.
14          */
15         void *Data; /**< the Data belonging to this storage */
16         DeleteHashDataFunc Destructor; /**< if we want to destroy Data, do it with this function. */
17 };
18
19 struct HashKey {
20         /**
21          * \brief Hash key element; sorted by Keye
22          */
23         long Key;         /**< Numeric Hashkey comperator for hash sorting */
24         long Position;    /**< Pointer to a Payload struct in the Payload Aray */
25         char *HashKey;    /**< the Plaintext Hashkey */
26         long HKLen;       /**< length of the Plaintext Hashkey */
27         Payload *PL;      /**< pointer to our payload for sorting */
28 };
29
30 struct HashList {
31         /**
32          * \brief Hash structure; holds arrays of Hashkey and Payload. 
33          */
34         Payload **Members;     /**< Our Payload members. This fills up linear */
35         HashKey **LookupTable; /**< Hash Lookup table. Elements point to members, and are sorted by their hashvalue */
36         char **MyKeys;         /**< this keeps the members for a call of GetHashKeys */
37         long nMembersUsed;     /**< how many pointers inside of the array are used? */
38         long MemberSize;       /**< how big is Members and LookupTable? */
39         long tainted;          /**< if 0, we're hashed, else s.b. else sorted us in his own way. */
40 };
41
42 struct HashPos {
43         /**
44          * \brief Anonymous Hash Iterator Object. used for traversing the whole array from outside 
45          */
46         long Position;
47 };
48
49 /**
50  * \brief verify the contents of a hash list; here for debugging purposes.
51  * \param Hash your Hashlist structure
52  * \param First Functionpointer to allow you to print your payload
53  * \param Second Functionpointer to allow you to print your payload
54  * \returns 0
55  */
56 int PrintHash(HashList *Hash, PrintHashContent First, PrintHashContent Second)
57 {
58         const char *foo;
59         const char *bar;
60         const char *bla = "";
61         long key;
62         long i;
63         if (Hash->MyKeys != NULL)
64                 free (Hash->MyKeys);
65
66         Hash->MyKeys = (char**) malloc(sizeof(char*) * Hash->nMembersUsed);
67 #ifdef DEBUG
68         printf("----------------------------------\n");
69 #endif
70         for (i=0; i < Hash->nMembersUsed; i++) {
71                 
72                 if (Hash->LookupTable[i] == NULL)
73                 {
74                         foo = "";
75                         bar = "";
76                         key = 0;
77                 }
78                 else 
79                 {
80                         key = Hash->LookupTable[i]->Key;
81                         foo = Hash->LookupTable[i]->HashKey;
82                         if (First != NULL)
83                                 bar = First(Hash->Members[Hash->LookupTable[i]->Position]->Data);
84                         if (Second != NULL)
85                                 bla = Second(Hash->Members[Hash->LookupTable[i]->Position]->Data);
86                 }
87 #ifdef DEBUG
88                 printf (" ---- Hashkey[%ld][%ld]: '%s' Value: '%s' ; %s\n", i, key, foo, bar, bla);
89 #endif
90         }
91 #ifdef DEBUG
92         printf("----------------------------------\n");
93 #endif
94         return 0;
95 }
96
97
98 /**
99  * \brief instanciate a new hashlist
100  * \returns the newly allocated list. 
101  */
102 HashList *NewHash(void) 
103 {
104         HashList *NewList;
105         NewList = malloc (sizeof(HashList));
106         memset(NewList, 0, sizeof(HashList));
107
108         NewList->Members = malloc(sizeof(Payload*) * 100);
109         memset(NewList->Members, 0, sizeof(Payload*) * 100);
110
111         NewList->LookupTable = malloc(sizeof(HashKey*) * 100);
112         memset(NewList->LookupTable, 0, sizeof(HashKey*) * 100);
113
114         NewList->MemberSize = 100;
115         NewList->tainted = 0;
116
117         return NewList;
118 }
119
120 /**
121  * \brief private destructor for one hash element.
122  * \param Data an element to free using the user provided destructor, or just plain free
123  */
124 static void DeleteHashPayload (Payload *Data)
125 {
126         /** do we have a destructor for our payload? */
127         if (Data->Destructor)
128                 Data->Destructor(Data->Data);
129         else
130                 free(Data->Data);
131 }
132
133 /**
134  * \brief destroy a hashlist and all of its members
135  * \param Hash Hash to destroy. Is NULL'ed so you are shure its done.
136  */
137 void DeleteHash(HashList **Hash)
138 {
139         int i;
140         HashList *FreeMe;
141
142         FreeMe = *Hash;
143         if (FreeMe == NULL)
144                 return;
145         for (i=0; i < FreeMe->nMembersUsed; i++)
146         {
147                 /** get rid of our payload */
148                 if (FreeMe->Members[i] != NULL)
149                 {
150                         DeleteHashPayload(FreeMe->Members[i]);
151                         free(FreeMe->Members[i]);
152                 }
153                 /** delete our hashing data */
154                 if (FreeMe->LookupTable[i] != NULL)
155                 {
156                         free(FreeMe->LookupTable[i]->HashKey);
157                         free(FreeMe->LookupTable[i]);
158                 }
159         }
160         /** now, free our arrays... */
161         free(FreeMe->LookupTable);
162         free(FreeMe->Members);
163         /** did s.b. want an array of our keys? free them. */
164         if (FreeMe->MyKeys != NULL)
165                 free(FreeMe->MyKeys);
166         /** buye bye cruel world. */    
167         free (FreeMe);
168         *Hash = NULL;
169 }
170
171
172 /**
173  * \brief private function to add a new item to / replace an existing in -  the hashlist
174  * if the hash list is full, its re-alloced with double size.
175  * \parame Hash our hashlist to manipulate
176  * \param HashPos where should we insert / replace?
177  * \param HashKeyStr the Hash-String
178  * \param HKLen length of HashKeyStr
179  * \param Data your Payload to add
180  * \param Destructor Functionpointer to free Data. if NULL, default free() is used.
181  */
182 static void InsertHashItem(HashList *Hash, 
183                            long HashPos, 
184                            long HashBinKey, 
185                            char *HashKeyStr, 
186                            long HKLen, 
187                            void *Data,
188                            DeleteHashDataFunc Destructor)
189 {
190         Payload *NewPayloadItem;
191         HashKey *NewHashKey;
192
193         if (Hash->nMembersUsed >= Hash->MemberSize)
194         {
195                 /* Ok, Our space is used up. Double the available space. */
196                 Payload **NewPayloadArea;
197                 HashKey **NewTable;
198
199                 /** double our payload area */
200                 NewPayloadArea = (Payload**) malloc(sizeof(Payload*) * Hash->MemberSize * 2);
201                 memset(&NewPayloadArea[Hash->MemberSize], 0, sizeof(Payload*) * Hash->MemberSize);
202                 memcpy(NewPayloadArea, Hash->Members, sizeof(Payload*) * Hash->MemberSize);
203                 free(Hash->Members);
204                 Hash->Members = NewPayloadArea;
205
206                 /** double our hashtable area */
207                 NewTable = malloc(sizeof(HashKey*) * Hash->MemberSize * 2);
208                 memset(&NewTable[Hash->MemberSize], 0, sizeof(HashKey*) * Hash->MemberSize);
209                 memcpy(NewTable, Hash->LookupTable, sizeof(HashKey*) * Hash->MemberSize);
210                 free(Hash->LookupTable);
211                 Hash->LookupTable = NewTable;
212
213                 Hash->MemberSize *= 2;
214         }
215         /** Arrange the payload */
216         NewPayloadItem = (Payload*) malloc (sizeof(Payload));
217         NewPayloadItem->Data = Data;
218         NewPayloadItem->Destructor = Destructor;
219         /** Arrange the hashkey */
220         NewHashKey = (HashKey*) malloc (sizeof(HashKey));
221         NewHashKey->HashKey = (char *) malloc (HKLen + 1);
222         NewHashKey->HKLen = HKLen;
223         memcpy (NewHashKey->HashKey, HashKeyStr, HKLen + 1);
224         NewHashKey->Key = HashBinKey;
225         NewHashKey->PL = NewPayloadItem;
226         /** our payload is queued at the end... */
227         NewHashKey->Position = Hash->nMembersUsed;
228         /** but if we should be sorted into a specific place... */
229         if ((Hash->nMembersUsed != 0) && 
230             (HashPos != Hash->nMembersUsed) ) {
231                 long InsertAt;
232                 long ItemsAfter;
233
234                 ItemsAfter = Hash->nMembersUsed - HashPos;
235                 InsertAt = HashPos;
236                 /** make space were we can fill us in */
237                 if (ItemsAfter > 0)
238                 {
239                         memmove(&Hash->LookupTable[InsertAt + 1],
240                                 &Hash->LookupTable[InsertAt],
241                                 ItemsAfter * sizeof(HashKey*));
242                 } 
243         }
244         
245         Hash->Members[Hash->nMembersUsed] = NewPayloadItem;
246         Hash->LookupTable[HashPos] = NewHashKey;
247         Hash->nMembersUsed++;
248 }
249
250 /**
251  * \brief if the user has tainted the hash, but wants to insert / search items by their key
252  *  we need to search linear through the array. You have been warned that this will take more time!
253  * \param Hash Our Hash to manipulate
254  * \param HashBinKey the Hash-Number to lookup. 
255  * \returns the position (most closely) matching HashBinKey (-> Caller needs to compare! )
256  */
257 static long FindInTaintedHash(HashList *Hash, long HashBinKey)
258 {
259         long SearchPos;
260
261         for (SearchPos = 0; SearchPos < Hash->nMembersUsed; SearchPos ++) {
262                 if (Hash->LookupTable[SearchPos]->Key == HashBinKey){
263                         return SearchPos;
264                 }
265         }
266         return SearchPos;
267 }
268
269 /**
270  * \brief Private function to lookup the Item / the closest position to put it in
271  * \param Hash Our Hash to manipulate
272  * \param HashBinKey the Hash-Number to lookup. 
273  * \returns the position (most closely) matching HashBinKey (-> Caller needs to compare! )
274  */
275 static long FindInHash(HashList *Hash, long HashBinKey)
276 {
277         long SearchPos;
278         long StepWidth;
279
280         if (Hash->tainted)
281                 return FindInTaintedHash(Hash, HashBinKey);
282
283         SearchPos = Hash->nMembersUsed / 2;
284         StepWidth = SearchPos / 2;
285         while ((SearchPos > 0) && 
286                (SearchPos < Hash->nMembersUsed)) 
287         {
288                 /** Did we find it? */
289                 if (Hash->LookupTable[SearchPos]->Key == HashBinKey){
290                         return SearchPos;
291                 }
292                 /** are we Aproximating in big steps? */
293                 if (StepWidth > 1){
294                         if (Hash->LookupTable[SearchPos]->Key > HashBinKey)
295                                 SearchPos -= StepWidth;
296                         else
297                                 SearchPos += StepWidth;
298                         StepWidth /= 2;                 
299                 }
300                 else { /** We are right next to our target, within 4 positions */
301                         if (Hash->LookupTable[SearchPos]->Key > HashBinKey) {
302                                 if ((SearchPos > 0) && 
303                                     (Hash->LookupTable[SearchPos - 1]->Key < HashBinKey))
304                                         return SearchPos;
305                                 SearchPos --;
306                         }
307                         else {
308                                 if ((SearchPos + 1 < Hash->nMembersUsed) && 
309                                     (Hash->LookupTable[SearchPos + 1]->Key > HashBinKey))
310                                         return SearchPos;
311                                 SearchPos ++;
312                         }
313                         StepWidth--;
314                 }
315         }
316         return SearchPos;
317 }
318
319 /**
320  * \brief private abstract wrapper around the hashing algorithm
321  * \param HKey the hash string
322  * \param HKLen length of HKey
323  * \returns the calculated hash value
324  */
325 inline static long CalcHashKey (char *HKey, long HKLen)
326 {
327         return hashlittle(HKey, HKLen, 9283457);
328 }
329
330
331 /**
332  * \brief Add a new / Replace an existing item in the Hash
333  * \param HashList the list to manipulate
334  * \param HKey the hash-string to store Data under
335  * \param HKeyLen Length of HKey
336  * \param Data the payload you want to associate with HKey
337  * \param DeleteIt if not free() should be used to delete Data set to NULL, else DeleteIt is used.
338  */
339 void Put(HashList *Hash, char *HKey, long HKLen, void *Data, DeleteHashDataFunc DeleteIt)
340 {
341         long HashBinKey;
342         long HashAt;
343
344         /** first, find out were we could fit in... */
345         HashBinKey = CalcHashKey(HKey, HKLen);
346         HashAt = FindInHash(Hash, HashBinKey);
347
348         /** oh, we're brand new... */
349         if (Hash->LookupTable[HashAt] == NULL){
350                 InsertHashItem(Hash, HashAt, HashBinKey, HKey, HKLen, Data, DeleteIt);
351         }/** Insert After? */
352         else if (Hash->LookupTable[HashAt]->Key > HashBinKey) {
353                 InsertHashItem(Hash, HashAt, HashBinKey, HKey, HKLen, Data, DeleteIt);
354         }/** Insert before? */
355         else if (Hash->LookupTable[HashAt]->Key < HashBinKey) {
356                 InsertHashItem(Hash, HashAt + 1, HashBinKey, HKey, HKLen, Data, DeleteIt);              
357         }
358         else { /** Ok, we have a colision. replace it. */
359                 long PayloadPos;
360
361                 PayloadPos = Hash->LookupTable[HashAt]->Position;
362                 DeleteHashPayload(Hash->Members[PayloadPos]);
363                 Hash->Members[PayloadPos]->Data = Data;
364                 Hash->Members[PayloadPos]->Destructor = DeleteIt;
365         }
366 }
367
368 /**
369  * \brief Lookup the Data associated with HKey
370  * \param Hash the Hashlist to search in
371  * \param HKey the hashkey to look up
372  * \param HKLen length of HKey
373  * \param Data returns the Data associated with HKey
374  * \returns 0 if not found, 1 if.
375  */
376 int GetHash(HashList *Hash, const char *HKey, long HKLen, void **Data)
377 {
378         long HashBinKey;
379         long HashAt;
380
381         /** first, find out were we could be... */
382         HashBinKey = CalcHashKey((char*)HKey, HKLen);
383         HashAt = FindInHash(Hash, HashBinKey);
384         if ((HashAt < 0) || /**< Not found at the lower edge? */
385             (HashAt >= Hash->nMembersUsed) || /**< Not found at the upper edge? */
386             (Hash->LookupTable[HashAt]->Key != HashBinKey)) { /**< somewhere inbetween but no match? */
387                 *Data = NULL;
388                 return 0;
389         }
390         else { /** GOTCHA! */
391                 long MemberPosition;
392
393                 MemberPosition = Hash->LookupTable[HashAt]->Position;
394                 *Data = Hash->Members[MemberPosition]->Data;
395                 return 1;
396         }
397 }
398
399 /* TODO? */
400 int GetKey(HashList *Hash, char *HKey, long HKLen, void **Payload)
401 {
402         return 0;
403 }
404
405 /**
406  * \brief get the Keys present in this hash, simila to array_keys() in PHP
407  *  Attention: List remains to Hash! don't modify or free it!
408  * \param Hash Your Hashlist to extract the keys from
409  * \param List returns the list of hashkeys stored in Hash
410  */
411 int GetHashKeys(HashList *Hash, char ***List)
412 {
413         long i;
414         if (Hash->MyKeys != NULL)
415                 free (Hash->MyKeys);
416
417         Hash->MyKeys = (char**) malloc(sizeof(char*) * Hash->nMembersUsed);
418         for (i=0; i < Hash->nMembersUsed; i++) {
419         
420                 Hash->MyKeys[i] = Hash->LookupTable[i]->HashKey;
421         }
422         *List = (char**)Hash->MyKeys;
423         return Hash->nMembersUsed;
424 }
425
426 /**
427  * \brief creates a hash-linear iterator object
428  * \returns the hash iterator
429  */
430 HashPos *GetNewHashPos(void)
431 {
432         HashPos *Ret;
433         
434         Ret = (HashPos*)malloc(sizeof(HashPos));
435         Ret->Position = 0;
436         return Ret;
437 }
438
439 /**
440  * \brief frees a linear hash iterator
441  */
442 void DeleteHashPos(HashPos **DelMe)
443 {
444         free(*DelMe);
445         *DelMe = NULL;
446 }
447
448
449 /**
450  * \brief Get the data located where HashPos Iterator points at, and Move HashPos one forward
451  * \param Hash your Hashlist to follow
452  * \param HKLen returns Length of Hashkey Returned
453  * \param HashKey returns the Hashkey corrosponding to HashPos
454  * \param Data returns the Data found at HashPos
455  * \returns whether the item was found or not.
456  */
457 int GetNextHashPos(HashList *Hash, HashPos *At, long *HKLen, char **HashKey, void **Data)
458 {
459         long PayloadPos;
460
461         if (Hash->nMembersUsed <= At->Position)
462                 return 0;
463         *HKLen = Hash->LookupTable[At->Position]->HKLen;
464         *HashKey = Hash->LookupTable[At->Position]->HashKey;
465         PayloadPos = Hash->LookupTable[At->Position]->Position;
466         *Data = Hash->Members[PayloadPos]->Data;
467         At->Position++;
468         return 1;
469 }
470
471 /**
472  * \brief sorting function for sorting the Hash alphabeticaly by their strings
473  * \param Key1 first item
474  * \param Key2 second item
475  */
476 static int SortByKeys(const void *Key1, const void* Key2)
477 {
478         HashKey *HKey1, *HKey2;
479         HKey1 = *(HashKey**) Key1;
480         HKey2 = *(HashKey**) Key2;
481
482         return strcasecmp(HKey1->HashKey, HKey2->HashKey);
483 }
484
485 /**
486  * \brief sorting function to regain hash-sequence and revert tainted status
487  * \param Key1 first item
488  * \param Key2 second item
489  */
490 static int SortByHashKeys(const void *Key1, const void* Key2)
491 {
492         HashKey *HKey1, *HKey2;
493         HKey1 = *(HashKey**) Key1;
494         HKey2 = *(HashKey**) Key2;
495
496         return HKey1->Key > HKey2->Key;
497 }
498
499
500 /**
501  * \brief sort the hash alphabeticaly by their keys.
502  * Caution: This taints the hashlist, so accessing it later 
503  * will be significantly slower! You can un-taint it by SortByHashKeyStr
504  * \param Hash the list to sort
505  */
506 void SortByHashKey(HashList *Hash)
507 {
508         if (Hash->nMembersUsed < 2)
509                 return;
510         qsort(Hash->LookupTable, Hash->nMembersUsed, sizeof(HashKey*), SortByKeys);
511         Hash->tainted = 1;
512 }
513
514 /**
515  * \brief sort the hash by their keys (so it regains untainted state).
516  * this will result in the sequence the hashing allgorithm produces it by default.
517  * \param Hash the list to sort
518  */
519 void SortByHashKeyStr(HashList *Hash)
520 {
521         Hash->tainted = 0;
522         if (Hash->nMembersUsed < 2)
523                 return;
524         qsort(Hash->LookupTable, Hash->nMembersUsed, sizeof(HashKey*), SortByHashKeys);
525 }
526
527
528 /**
529  * \brief gives user sort routines access to the hash payload
530  * \param Searchentry to retrieve Data to
531  * \returns Data belonging to HashVoid
532  */
533 const void *GetSearchPayload(const void *HashVoid)
534 {
535         return (*(HashKey**)HashVoid)->PL->Data;
536 }
537
538 /**
539  * \brief sort the hash by your sort function. see the following sample.
540  * this will result in the sequence the hashing allgorithm produces it by default.
541  * \param Hash the list to sort
542  * \param SortBy Sortfunction; see below how to implement this
543  */
544 void SortByPayload(HashList *Hash, CompareFunc SortBy)
545 {
546         if (Hash->nMembersUsed < 2)
547                 return;
548         qsort(Hash->LookupTable, Hash->nMembersUsed, sizeof(HashKey*), SortBy);
549         Hash->tainted = 1;
550 }
551
552
553
554
555 /**
556  * given you've put char * into your hash as a payload, a sort function might
557  * look like this:
558  * int SortByChar(const void* First, const void* Second)
559  * {
560  *      char *a, *b;
561  *      a = (char*) GetSearchPayload(First);
562  *      b = (char*) GetSearchPayload(Second);
563  *      return strcmp (a, b);
564  * }
565  */