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