WordDBCompress.cc

Go to the documentation of this file.
00001 //
00002 // WordDBCompress.cc
00003 //
00004 // Part of the ht://Dig package   <http://www.htdig.org/>
00005 // Copyright (c) 1999 The ht://Dig Group
00006 // For copyright details, see the file COPYING in your distribution
00007 // or the GNU Public License version 2 or later
00008 // <http://www.gnu.org/copyleft/gpl.html>
00009 //
00010 // $Id: WordDBCompress_8cc-source.html,v 1.1 2008/06/08 10:13:02 sebdiaz Exp $
00011 //
00012 /*
00013 
00014  BTREE original page layout in Berkeley DB
00015 
00016  *      +-----------------------------------+
00017  *      |    lsn    |   pgno    | prev pgno |
00018  *      +-----------------------------------+
00019  *      | next pgno |  entries  | hf offset |
00020  *      +-----------------------------------+
00021  *      |   level   |   type    |   index   |
00022  *      +-----------------------------------+
00023  *      |   index   | free -->              |
00024  *      +-----------+-----------------------+
00025  *      |        F R E E A R E A            |
00026  *      +-----------------------------------+
00027  *      |              <-- free |   item    |
00028  *      +-----------------------------------+
00029  *      |   item    |   item    |   item    |
00030  *      +-----------------------------------+
00031  */
00032 #ifdef HAVE_CONFIG_H
00033 #include "config.h"
00034 #endif /* HAVE_CONFIG_H */
00035 
00036 #include <stdlib.h>
00037 #include <errno.h>
00038 
00039 extern "C" {
00040 #include "db_int.h"
00041 #include "db_page.h"
00042 }
00043 
00044 #include "lib.h"
00045 #include "WordDBCompress.h"
00046 #include "WordBitCompress.h"
00047 #include "WordKeyInfo.h"
00048 #include "WordKey.h"
00049 #include "WordRecord.h"
00050 #include "WordDB.h"
00051 #include "HtMaxMin.h"
00052 
00053 /*
00054  *   WordDBCompress: C-callbacks, actually called by Berkeley-DB
00055  *      they just call their WordDBCompress equivalents (by using user_data)
00056  */
00057 extern "C"
00058 {
00059 
00060 int WordDBCompress_compress_c(DB_ENV*, const u_int8_t* inbuff, int inbuff_length, u_int8_t** outbuffp, int* outbuff_lengthp, void *user_data)
00061 {
00062     if(!user_data) {
00063       fprintf(stderr, "WordDBCompress_compress_c:: user_data is NULL");
00064       return NOTOK;
00065     }
00066     return ((WordDBCompress *)user_data)->Compress((unsigned char*)inbuff, inbuff_length, (unsigned char**)outbuffp, outbuff_lengthp);
00067 }
00068 
00069 int WordDBCompress_uncompress_c(DB_ENV*, const u_int8_t* inbuff, int inbuff_length, u_int8_t* outbuff, int outbuff_length, void *user_data)
00070 {
00071     if(!user_data) {
00072       fprintf(stderr, "WordDBCompress_uncompress_c:: user_data is NULL");
00073       return NOTOK;
00074     }
00075     return ((WordDBCompress *)user_data)->Uncompress((unsigned char *)inbuff, inbuff_length, (unsigned char*)outbuff, outbuff_length);
00076 }
00077 
00078 }
00079 
00080 //
00081 // Symbolic names for the data in WordDBEncoded.values[..] arrays
00082 //
00083 //
00084 // Each entry in a leaf/internal page has a flag that shows how
00085 // the entry is coded.
00086 //
00087 #define WORD_CMPR_VAL_FLAGS     0
00088 //
00089 // Btree: Numerical fields of WordKey
00090 //
00091 #define WORD_CMPR_VAL_FIELDS    1
00092 //
00093 // Btree/Internal: BINTERNAL.pgno
00094 //
00095 #define WORD_CMPR_VAL_PGNO      (WORD_CMPR_VAL_FIELDS + WORD_KEY_MAX_NFIELDS)
00096 //
00097 // Btree/Leaf: Length of record string
00098 //
00099 #define WORD_CMPR_VAL_RLENGTH   WORD_CMPR_VAL_PGNO
00100 //
00101 // Btree/Internal: BINTERNAL.nrecs
00102 //
00103 #define WORD_CMPR_VAL_NRECS     (WORD_CMPR_VAL_PGNO + 1)
00104 //
00105 // Btree/Leaf: Value of integer if record contains a single integer value
00106 //
00107 #define WORD_CMPR_VAL_RVALUE    WORD_CMPR_VAL_NRECS
00108 //
00109 // Btree: length of WordKey.GetWord() prefix shared with previous entry
00110 //
00111 #define WORD_CMPR_VAL_PREFIX    (WORD_CMPR_VAL_PGNO + 2)
00112 //
00113 // Btree: length of WordKey.GetWord() suffix to add to previous key
00114 // shared prefix.
00115 //
00116 #define WORD_CMPR_VAL_SUFFIX    (WORD_CMPR_VAL_PGNO + 3)
00117 //
00118 // Symbolic name for the last index 
00119 //
00120 #define WORD_CMPR_VAL_LAST      WORD_CMPR_VAL_SUFFIX
00121 
00122 #define WORD_CMPR_VAL_ARRAY_SIZE (WORD_CMPR_VAL_LAST + 1)
00123 //
00124 // Number of bits needed to encode the maximum array size value
00125 //
00126 #define WORD_CMPR_VAL_ARRAY_SIZE_BITS 8
00127 
00128 //
00129 // Number of bits used in flags
00130 //
00131 #define WORD_CMPR_VAL_FLAGS_BITS (WORD_CMPR_VAL_LAST + 1)
00132 //
00133 // Values for the WORD_CMPR_VAL_FLAGS bit field
00134 //
00135 #define WORD_CMPR_VAL_FLAGS_FIELD(n)    (1 << (WORD_CMPR_VAL_FIELDS + (n)))
00136 #define WORD_CMPR_VAL_FLAGS_PGNO        (1 << (WORD_CMPR_VAL_PGNO))
00137 #define WORD_CMPR_VAL_FLAGS_RLENGTH     WORD_CMPR_VAL_FLAGS_PGNO
00138 #define WORD_CMPR_VAL_FLAGS_NRECS       (1 << (WORD_CMPR_VAL_NRECS))
00139 #define WORD_CMPR_VAL_FLAGS_RVALUE      WORD_CMPR_VAL_FLAGS_NRECS
00140 #define WORD_CMPR_VAL_FLAGS_PREFIX      (1 << (WORD_CMPR_VAL_PREFIX))
00141 #define WORD_CMPR_VAL_FLAGS_SUFFIX      (1 << (WORD_CMPR_VAL_SUFFIX))
00142 #define WORD_CMPR_VAL_FLAGS_STRING      (1 << (WORD_CMPR_VAL_LAST + 1))
00143 #define WORD_CMPR_VAL_FLAGS_EMPTY       (1 << (WORD_CMPR_VAL_LAST + 2))
00144 #define WORD_CMPR_VAL_FLAGS_RECORD_EQ   (1 << (WORD_CMPR_VAL_LAST + 3))
00145 #define WORD_CMPR_VAL_FLAGS_RECORD_NO   (1 << (WORD_CMPR_VAL_LAST + 4))
00146 #define WORD_CMPR_VAL_FLAGS_RECORD_STR  (1 << (WORD_CMPR_VAL_LAST + 5))
00147 
00148 //
00149 // Storage for values extracted from a PAGE structure
00150 //
00151 class WordDBEncoded {
00152 public:
00153   inline WordDBEncoded() {
00154     Init();
00155   }
00156   inline ~WordDBEncoded() {
00157     free(strings);
00158     for(int i = 0; i < WORD_CMPR_VAL_ARRAY_SIZE; i++)
00159       free(values[i]);
00160   }
00161 
00162   inline void Init() {
00163     strings_size = 32;
00164     strings = (unsigned char*)malloc(strings_size);
00165 
00166     for(int i = 0; i < WORD_CMPR_VAL_ARRAY_SIZE; i++) {
00167       values_size[i] = 32;
00168       values[i] = (unsigned int*)malloc(values_size[i] * sizeof(unsigned int));
00169     }
00170 
00171     Clear();
00172   }
00173 
00174   inline void Clear() {
00175     strings_length = 0;
00176     strings_idx = 0;
00177 
00178     for(int i = 0; i < WORD_CMPR_VAL_ARRAY_SIZE; i++) {
00179       values_length[i] = 0;
00180       values_idx[i] = 0;
00181     }    
00182   }
00183 
00184   inline void AllocateStrings(unsigned int size) {
00185     strings_size = size;
00186     strings = (unsigned char*)realloc(strings, strings_size);
00187   }
00188 
00189   inline void CheckStrings(int index) {
00190     while(index >= strings_size)
00191       AllocateStrings(strings_size * 2);
00192   }
00193 
00194   inline void AllocateValues(int what, unsigned int size) {
00195     values_size[what] = size;
00196     values[what] = (unsigned int*)realloc(values[what], values_size[what] * sizeof(unsigned int));
00197   }
00198 
00199   inline void CheckValues(int what, int index) {
00200     while(index >= values_size[what])
00201       AllocateValues(what, values_size[what] * 2);
00202   }
00203 
00204   inline void PushValue(int what, unsigned int value) {
00205     CheckValues(what, values_length[what]);
00206     values[what][values_length[what]] = value;
00207     values_length[what]++;
00208   }
00209 
00210   inline void PushString(const unsigned char* string, int length) {
00211     CheckStrings(strings_length + length);
00212     memcpy(strings + strings_length, string, length);
00213     strings_length += length;
00214   }
00215 
00216   inline unsigned int ShiftValue(int what) {
00217     if(values_idx[what] >= values_length[what]) {
00218       fprintf(stderr, "WordDBEncoded::ShiftValue: what = %d, (idx = %d) >= (length = %d)\n", what, values_idx[what], values_length[what]);
00219       abort();
00220     }
00221     return values[what][values_idx[what]++];
00222   }
00223 
00224   inline unsigned char* ShiftString(int length) {
00225     if(strings_idx + length > strings_length) {
00226       fprintf(stderr, "WordDBEncoded::ShiftString: (idx + length = %d) >= (length = %d)\n", strings_idx + length, strings_length);
00227       abort();
00228     }
00229     unsigned char* string = strings + strings_idx;
00230     strings_idx += length;
00231     return string;
00232   }
00233   
00234   void Put(WordBitCompress& stream);
00235   void Get(WordBitCompress& stream);
00236   
00237   unsigned int *values[WORD_CMPR_VAL_ARRAY_SIZE];
00238   int values_length[WORD_CMPR_VAL_ARRAY_SIZE];
00239   int values_idx[WORD_CMPR_VAL_ARRAY_SIZE];
00240   int values_size[WORD_CMPR_VAL_ARRAY_SIZE];
00241 
00242   unsigned char *strings;
00243   int strings_length;
00244   int strings_idx;
00245   int strings_size;
00246 };
00247 
00248 void WordDBEncoded::Put(WordBitCompress& stream)
00249 {
00250   unsigned int count = 0;
00251   for(int i = 0; i < WORD_CMPR_VAL_ARRAY_SIZE; i++) {
00252     if(values_length[i] > 0) count++;
00253   }
00254   stream.WordBitStream::PutUint(count, WORD_CMPR_VAL_ARRAY_SIZE_BITS);
00255 
00256   for(int i = 0; i < WORD_CMPR_VAL_ARRAY_SIZE; i++) {
00257     if(values_length[i] > 0) {
00258       stream.WordBitStream::PutUint(i, WORD_CMPR_VAL_ARRAY_SIZE_BITS);      
00259       stream.PutUints(values[i], values_length[i]);
00260     }
00261   }
00262 
00263   stream.PutUchars(strings, strings_length);  
00264 }
00265 
00266 void WordDBEncoded::Get(WordBitCompress& stream)
00267 {
00268   Clear();
00269 
00270   unsigned int count = 0;
00271   count = stream.WordBitStream::GetUint(WORD_CMPR_VAL_ARRAY_SIZE_BITS);
00272 
00273   for(unsigned int i = 0; i < count; i++) {
00274     unsigned int index = stream.WordBitStream::GetUint(WORD_CMPR_VAL_ARRAY_SIZE_BITS);
00275     values_length[index] = stream.GetUints(&values[index], &values_size[index]);
00276   }
00277 
00278   strings_length = stream.GetUchars(&strings, &strings_size);
00279 }
00280 
00281 //
00282 // ******************** WordDBCompress implementation **************
00283 //
00284 
00285 #define WORD_CMPR_OVERHEAD      ((int)sizeof(unsigned char))
00286 
00287 WordDBCompress::WordDBCompress(WordContext* ncontext)
00288 {
00289   cmprInfo = 0;
00290   context = ncontext;
00291   encoded = new WordDBEncoded();
00292 
00293   const Configuration& config = context->GetConfiguration();
00294   debug = config.Boolean("wordlist_compress_debug", 0);
00295   verbose = config.Value("wordlist_compress_verbose", 0);
00296 }
00297 
00298 WordDBCompress::~WordDBCompress()
00299 {
00300   delete encoded;
00301 }
00302 
00303 int 
00304 WordDBCompress::Compress(const  unsigned char *inbuff, int inbuff_length, unsigned char **outbuffp, int *outbuff_lengthp)
00305 {
00306   //
00307   // Maximum output length can be WORD_CMPR_OVERHEAD more than input
00308   //
00309   int outbuff_length = inbuff_length + WORD_CMPR_OVERHEAD;
00310   unsigned char* outbuff = (unsigned char*)malloc(sizeof(unsigned char) * outbuff_length);
00311 
00312   *outbuffp = 0;
00313   *outbuff_lengthp = outbuff_length;
00314 
00315   if(outbuff == 0)
00316     return ENOMEM;
00317 
00318   int ret = 0;
00319   PAGE* pp = (PAGE*)inbuff;
00320 
00321   *outbuff = TYPE_TAGS(pp);
00322 
00323   switch(TYPE_TAGS(pp)) {
00324   case P_IBTREE|WORD_DB_INDEX:
00325   case P_LBTREE|WORD_DB_INDEX:
00326     ret = CompressBtree(inbuff, inbuff_length, outbuff, &outbuff_length);
00327     break;
00328   default:
00329     memcpy((char*)(outbuff + WORD_CMPR_OVERHEAD), (char*)inbuff, inbuff_length);
00330     break;
00331   }
00332 
00333   if(ret == 0) {
00334     *outbuffp = outbuff;
00335     *outbuff_lengthp = outbuff_length;
00336   } else {
00337     free(outbuff);
00338   }
00339 
00340   return ret;
00341 }
00342 
00343 int 
00344 WordDBCompress::Uncompress(const unsigned char *inbuff, int inbuff_length, unsigned char *outbuff, int outbuff_length)
00345 {
00346   int ret = 0;
00347 
00348   switch(*inbuff) {
00349   case P_IBTREE|WORD_DB_INDEX:
00350   case P_LBTREE|WORD_DB_INDEX:
00351     ret = UncompressBtree(inbuff, inbuff_length, outbuff, outbuff_length);
00352     break;
00353   default:
00354     memcpy((char*)outbuff, (char*)(inbuff + WORD_CMPR_OVERHEAD), outbuff_length);
00355     break;
00356   }
00357 
00358   return ret;
00359 }
00360 
00361 int 
00362 WordDBCompress::CompressBtree(const  unsigned char *inbuff, int inbuff_length, unsigned char *outbuff, int *outbuff_lengthp)
00363 {
00364   int ret = 0;
00365   PAGE *pp = (PAGE *)inbuff;
00366 
00367   if(verbose) fprintf(stderr, "WordDBCompress::CompressBtree: page %d\n", PGNO(pp));
00368 
00369   switch(TYPE(pp)) {
00370   case P_IBTREE:
00371     ret = CompressIBtree(inbuff, inbuff_length, outbuff, outbuff_lengthp);
00372     break;
00373   case P_LBTREE:
00374     ret = CompressLBtree(inbuff, inbuff_length, outbuff, outbuff_lengthp);
00375     break;
00376   }
00377 
00378   return ret;
00379 }
00380 
00381 //
00382 // Return the length of the chars in b that are not common with a
00383 // a = abcdef
00384 // b = abcghi
00385 //        ---
00386 // return 3
00387 //
00388 static inline unsigned int suffixlength(const String& a, const String& b)
00389 {
00390   const unsigned char* ap = (const unsigned char*)a.get();
00391   const unsigned char* bp = (const unsigned char*)b.get();
00392   int length = HtMIN(a.length(), b.length());
00393 
00394   int i;
00395   for(i = 0; i < length && ap[i] == bp[i]; i++)
00396     ;
00397 
00398   return b.length() - i;
00399 }
00400 
00401 int 
00402 WordDBCompress::CompressIBtree(const  unsigned char *inbuff, int inbuff_length, unsigned char *outbuff, int *outbuff_lengthp)
00403 {
00404   PAGE *pp = (PAGE *)inbuff;
00405 
00406   if(verbose > 5) DumpPage(inbuff);
00407 
00408   WordBitCompress stream(inbuff_length);
00409 
00410   stream.PutUint(pp->lsn.file, sizeof(pp->lsn.file) * 8);
00411   stream.PutUint(pp->lsn.offset, sizeof(pp->lsn.file) * 8);
00412   stream.PutUint(PGNO(pp), sizeof(PGNO(pp)) * 8);
00413   stream.PutUint(NUM_ENT(pp), sizeof(NUM_ENT(pp)) * 8);
00414   stream.PutUint(LEVEL(pp), sizeof(LEVEL(pp)) * 8);
00415 
00416   if(NUM_ENT(pp) > 0) {
00417     int i;
00418     WordKey key(context);
00419     WordKey previous_key(context);
00420     BINTERNAL* previous_e = 0;
00421 
00422     encoded->Clear();
00423 
00424     for (i = 0; i < NUM_ENT(pp); i++) {
00425       BINTERNAL* e = GET_BINTERNAL(pp, i);
00426       if(debug) {
00427         if(e->type != B_KEYDATA)
00428           fprintf(stderr, "WordDBCompress::EncodeIBtree: unexpected type 0x%02x\n", e->type);
00429       }
00430       unsigned int changes = 0;
00431 
00432       if(e->len <= 0) {
00433         changes = WORD_CMPR_VAL_FLAGS_EMPTY;
00434         //
00435         // Structural data
00436         //
00437         encoded->PushValue(WORD_CMPR_VAL_PGNO, e->pgno);
00438         encoded->PushValue(WORD_CMPR_VAL_NRECS, e->nrecs);
00439       } else {
00440       
00441         key.Unpack((const char*)e->data, (int)e->len);
00442 
00443         if(previous_e != 0) {
00444           int is_first_change = 1;
00445 #if 0
00446           //
00447           // String
00448           //
00449           const String& word = key.GetWord();
00450           const String& previous_word = previous_key.GetWord();
00451           if(word != previous_word) {
00452             unsigned int suffix_length = suffixlength(previous_word, word);
00453             unsigned int prefix_length = word.length() - suffix_length;
00454             encoded->PushValue(WORD_CMPR_VAL_PREFIX, prefix_length);
00455             encoded->PushValue(WORD_CMPR_VAL_SUFFIX, suffix_length);
00456             const char* suffix = word.get() + prefix_length;
00457             encoded->PushString((const unsigned char*)suffix, suffix_length);
00458             changes |= WORD_CMPR_VAL_FLAGS_STRING;
00459             is_first_change = 0;
00460           }
00461 #endif
00462           //
00463           // Fields
00464           //
00465           int nfields = key.NFields();
00466           for(int i = 0; i < nfields; i++) {
00467             unsigned int value = key.Get(i);
00468             unsigned int previous_value = previous_key.Get(i);
00469             if(value != previous_value) {
00470               if(is_first_change) {
00471                 value -= previous_value;
00472                 is_first_change = 0;
00473               }
00474               encoded->PushValue(i + WORD_CMPR_VAL_FIELDS, value);
00475               changes |= WORD_CMPR_VAL_FLAGS_FIELD(i);
00476             }
00477           }
00478           //
00479           // Structural data
00480           //
00481           if(e->pgno != previous_e->pgno) {
00482             encoded->PushValue(WORD_CMPR_VAL_PGNO, e->pgno);
00483             changes |= WORD_CMPR_VAL_FLAGS_PGNO;
00484           }
00485           if(e->nrecs != previous_e->nrecs) {
00486             encoded->PushValue(WORD_CMPR_VAL_NRECS, e->nrecs);
00487             changes |= WORD_CMPR_VAL_FLAGS_NRECS;
00488           }
00489         } else {
00490 #if 0
00491           //
00492           // String
00493           //
00494           const String& word = key.GetWord();
00495           encoded->PushValue(WORD_CMPR_VAL_SUFFIX, word.length());
00496           encoded->PushString((const unsigned char*)word.get(), word.length());
00497 #endif
00498           //
00499           // Fields
00500           //
00501           int nfields = key.NFields();
00502           for(int i = 0; i < nfields; i++) {
00503             encoded->PushValue(i + WORD_CMPR_VAL_FIELDS, key.Get(i));
00504           }
00505           //
00506           // Structural data
00507           //
00508           encoded->PushValue(WORD_CMPR_VAL_PGNO, e->pgno);
00509           encoded->PushValue(WORD_CMPR_VAL_NRECS, e->nrecs);
00510         }
00511 
00512         previous_e = e;
00513         previous_key = key;
00514       }
00515 
00516       encoded->PushValue(WORD_CMPR_VAL_FLAGS, changes);
00517     }
00518 
00519     encoded->Put(stream);
00520   }
00521 
00522   int outbuff_length = *outbuff_lengthp;
00523   if(stream.BuffLength() > (outbuff_length - WORD_CMPR_OVERHEAD)) {
00524     fprintf(stderr, "WordDBCompress::CompressIBtree: compressed length = %d > available length = %d\n", stream.BuffLength(), outbuff_length - WORD_CMPR_OVERHEAD);
00525     abort();
00526   }
00527   memcpy(outbuff + WORD_CMPR_OVERHEAD, stream.Buff(), stream.BuffLength());
00528   outbuff_length = WORD_CMPR_OVERHEAD + stream.BuffLength();
00529 
00530   if(debug) {
00531     unsigned char* tmp = (unsigned char*)malloc(inbuff_length);
00532     memset((char*)tmp, '\0', inbuff_length);
00533     UncompressIBtree(outbuff, outbuff_length, tmp, inbuff_length);
00534     if(DiffPage(inbuff, tmp)) {
00535       fprintf(stderr, "WordDBCompress::CompressIBtree: failed\n");
00536       DumpPage(inbuff);
00537       DumpPage(tmp);
00538       exit(1);
00539     }
00540     free(tmp);
00541   }
00542 
00543   *outbuff_lengthp = outbuff_length;
00544 
00545   return 0;
00546 }
00547 
00548 int 
00549 WordDBCompress::CompressLBtree(const  unsigned char *inbuff, int inbuff_length, unsigned char *outbuff, int *outbuff_lengthp)
00550 {
00551   PAGE *pp = (PAGE *)inbuff;
00552 
00553   if(verbose > 5) DumpPage(inbuff);
00554 
00555   WordBitCompress stream(inbuff_length);
00556 
00557   stream.PutUint(pp->lsn.file, sizeof(pp->lsn.file) * 8);
00558   stream.PutUint(pp->lsn.offset, sizeof(pp->lsn.file) * 8);
00559   stream.PutUint(PGNO(pp), sizeof(PGNO(pp)) * 8);
00560   stream.PutUint(PREV_PGNO(pp), sizeof(PREV_PGNO(pp)) * 8);
00561   stream.PutUint(NEXT_PGNO(pp), sizeof(NEXT_PGNO(pp)) * 8);
00562   stream.PutUint(NUM_ENT(pp), sizeof(NUM_ENT(pp)) * 8);
00563   stream.PutUint(LEVEL(pp), sizeof(LEVEL(pp)) * 8);
00564 
00565   if(NUM_ENT(pp) > 0) {
00566     int i;
00567     WordKey key(context);
00568     WordKey previous_key(context);
00569     WordRecord record(context);
00570     WordRecord previous_record(context);
00571     BKEYDATA* previous_key_e = 0;
00572     BKEYDATA* previous_record_e = 0;
00573 
00574     encoded->Clear();
00575 
00576     for (i = 0; i < NUM_ENT(pp); i += P_INDX) {
00577       unsigned int changes = 0;
00578 
00579       {
00580         BKEYDATA* key_e = GET_BKEYDATA(pp, i);
00581         if(debug) {
00582           if(key_e->type != B_KEYDATA)
00583             fprintf(stderr, "WordDBCompress::EncodeLBtree: unexpected type 0x%02x\n", key_e->type);
00584           if(key_e->len <= 0)
00585             fprintf(stderr, "WordDBCompress::EncodeLBtree: unexpected length <= 0\n");
00586         }
00587 
00588         key.Unpack((const char*)key_e->data, (int)key_e->len);
00589 
00590         if(previous_key_e != 0) {
00591           int is_first_change = 1;
00592 #if 0
00593           //
00594           // String
00595           //
00596           const String& word = key.GetWord();
00597           const String& previous_word = previous_key.GetWord();
00598           if(word != previous_word) {
00599             unsigned int suffix_length = suffixlength(previous_word, word);
00600             unsigned int prefix_length = word.length() - suffix_length;
00601             encoded->PushValue(WORD_CMPR_VAL_PREFIX, prefix_length);
00602             encoded->PushValue(WORD_CMPR_VAL_SUFFIX, suffix_length);
00603             const char* suffix = word.get() + prefix_length;
00604             encoded->PushString((const unsigned char*)suffix, suffix_length);
00605             changes |= WORD_CMPR_VAL_FLAGS_STRING;
00606             is_first_change = 0;
00607           }
00608 #endif
00609           //
00610           // Fields
00611           //
00612           int nfields = key.NFields();
00613           for(int i = 0; i < nfields; i++) {
00614             unsigned int value = key.Get(i);
00615             unsigned int previous_value = previous_key.Get(i);
00616             if(value != previous_value) {
00617               if(is_first_change) {
00618                 value -= previous_value;
00619                 is_first_change = 0;
00620               }
00621               encoded->PushValue(WORD_CMPR_VAL_FIELDS + i, value);
00622               changes |= WORD_CMPR_VAL_FLAGS_FIELD(i);
00623             }
00624           }
00625         } else {
00626 #if 0
00627           //
00628           // String
00629           //
00630           const String& word = key.GetWord();
00631           encoded->PushValue(WORD_CMPR_VAL_SUFFIX, word.length());
00632           encoded->PushString((const unsigned char*)word.get(), word.length());
00633 #endif
00634           //
00635           // Fields
00636           //
00637           int nfields = key.NFields();
00638           for(int i = 0; i < nfields; i++) {
00639             encoded->PushValue(i + WORD_CMPR_VAL_FIELDS, key.Get(i));
00640           }
00641         }
00642 
00643         previous_key_e = key_e;
00644         previous_key = key;
00645       }
00646       {
00647         BKEYDATA* record_e = GET_BKEYDATA(pp, i + 1);
00648         if(debug) {
00649           if(record_e->type != B_KEYDATA)
00650             fprintf(stderr, "WordDBCompress::EncodeLBtree: unexpected type 0x%02x\n", record_e->type);
00651         }
00652 
00653         if(record_e->len <= 0) {
00654           changes |= WORD_CMPR_VAL_FLAGS_RECORD_NO;
00655         } else {
00656           record.Unpack((const char*)record_e->data, (int)record_e->len);
00657 
00658           switch(record.type) {
00659           case WORD_RECORD_DATA:
00660             if(previous_record_e &&
00661                record.info.data == previous_record.info.data) {
00662               changes |= WORD_CMPR_VAL_FLAGS_RECORD_EQ;
00663             } else {
00664               encoded->PushValue(WORD_CMPR_VAL_RVALUE, record.info.data);
00665             }
00666             break;
00667           case WORD_RECORD_STR:
00668             changes |= WORD_CMPR_VAL_FLAGS_RECORD_STR;
00669             if(previous_record_e &&
00670                record.info.str == previous_record.info.str) {
00671               changes |= WORD_CMPR_VAL_FLAGS_RECORD_EQ;
00672             } else {
00673               const String& string = record.info.str;
00674               encoded->PushValue(WORD_CMPR_VAL_RLENGTH, string.length());
00675               encoded->PushString((const unsigned char*)string.get(), string.length());
00676             }
00677             break;
00678           default:
00679             fprintf(stderr, "WordDBCompress::EncodeLBtree: unexpected record.type = %d\n", record.type);
00680             break;
00681           }
00682 
00683           previous_record = record;
00684           previous_record_e = record_e;
00685         }
00686 
00687       }
00688       encoded->PushValue(WORD_CMPR_VAL_FLAGS, changes);
00689     }
00690 
00691     encoded->Put(stream);
00692   }
00693 
00694   int outbuff_length = *outbuff_lengthp;
00695   if(stream.BuffLength() > (outbuff_length - WORD_CMPR_OVERHEAD)) {
00696     fprintf(stderr, "WordDBCompress::CompressLBtree: compressed length = %d > available length = %d\n", stream.BuffLength(), outbuff_length - WORD_CMPR_OVERHEAD);
00697     abort();
00698   }
00699   memcpy(outbuff + WORD_CMPR_OVERHEAD, stream.Buff(), stream.BuffLength());
00700   outbuff_length = WORD_CMPR_OVERHEAD + stream.BuffLength();
00701 
00702   if(debug) {
00703     unsigned char* tmp = (unsigned char*)malloc(inbuff_length);
00704     memset((char*)tmp, '\0', inbuff_length);
00705     UncompressLBtree(outbuff, outbuff_length, tmp, inbuff_length);
00706     if(DiffPage(inbuff, tmp)) {
00707       fprintf(stderr, "WordDBCompress::CompressLBtree: failed\n");
00708       DumpPage(inbuff);
00709       DumpPage(tmp);
00710       exit(1);
00711     }
00712     free(tmp);
00713   }
00714 
00715   *outbuff_lengthp = outbuff_length;
00716 
00717   return 0;
00718 }
00719 
00720 int 
00721 WordDBCompress::UncompressBtree(const unsigned char *inbuff, int inbuff_length, unsigned char *outbuff, int outbuff_length)
00722 {
00723   int ret = 0;
00724   switch((*inbuff) & TYPE_MASK) {
00725   case P_IBTREE:
00726     ret = UncompressIBtree(inbuff, inbuff_length, outbuff, outbuff_length);
00727     break;
00728   case P_LBTREE:
00729     ret = UncompressLBtree(inbuff, inbuff_length, outbuff, outbuff_length);
00730     break;
00731   }
00732   if(verbose) fprintf(stderr, "WordDBCompress::UncompressBtree: page %d\n", PGNO((PAGE*)outbuff));
00733   return ret;
00734 }
00735 
00736 /*
00737  * cdb___db_pitem --
00738  *      Put an item on a page.
00739  */
00740 static void
00741 cdb___db_pitem(PAGE *pagep, u_int32_t indx, u_int32_t nbytes, DBT *hdr, DBT *data)
00742 {
00743   u_int8_t *p;
00744 
00745   HOFFSET(pagep) -= nbytes;
00746   pagep->inp[indx] = HOFFSET(pagep);
00747   ++NUM_ENT(pagep);
00748 
00749   p = P_ENTRY(pagep, indx);
00750   memcpy(p, hdr->data, hdr->size);
00751   if (data != NULL)
00752     memcpy(p + hdr->size, data->data, data->size);
00753 }
00754 
00755 int 
00756 WordDBCompress::UncompressIBtree(const unsigned char *inbuff, int inbuff_length, unsigned char *outbuff, int outbuff_length)
00757 {
00758   memset((char*)outbuff, '\0', outbuff_length);
00759 
00760   WordBitCompress stream(inbuff_length);
00761   stream.BuffSet(inbuff + WORD_CMPR_OVERHEAD, inbuff_length - WORD_CMPR_OVERHEAD);
00762 
00763   PAGE *pp = (PAGE *)outbuff;
00764 
00765   TYPE_TAGS(pp) = *inbuff;
00766   HOFFSET(pp) = outbuff_length;
00767   short entry_count = 0;
00768   pp->lsn.file = stream.GetUint(sizeof(pp->lsn.file) * 8);
00769   pp->lsn.offset = stream.GetUint(sizeof(pp->lsn.offset) * 8);
00770   PGNO(pp) = stream.GetUint(sizeof(PGNO(pp)) * 8);
00771   entry_count = stream.GetUint(sizeof(NUM_ENT(pp)) * 8);
00772   LEVEL(pp) = stream.GetUint(sizeof(LEVEL(pp)) * 8);
00773   NEXT_PGNO(pp) = PREV_PGNO(pp) = 0;
00774 
00775   if(entry_count > 0) {
00776     int i;
00777     String packed;
00778     BINTERNAL previous_bi;
00779     BINTERNAL bi;
00780     DBT hdr;
00781     hdr.data = &bi;
00782     hdr.size = SSZA(BINTERNAL, data);
00783     DBT data;
00784     WordKey key(context);
00785     WordKey previous_key(context);
00786     String word;
00787 
00788     encoded->Get(stream);
00789 
00790     for(i = 0; i < entry_count; i++) {
00791       unsigned int changes = encoded->ShiftValue(WORD_CMPR_VAL_FLAGS);
00792 
00793       memset((char*)&bi, '\0', sizeof(BINTERNAL));
00794 
00795       if(changes & WORD_CMPR_VAL_FLAGS_EMPTY) {
00796         packed.trunc();
00797         //
00798         // Structural data
00799         //
00800         bi.pgno = encoded->ShiftValue(WORD_CMPR_VAL_PGNO);
00801         bi.nrecs = encoded->ShiftValue(WORD_CMPR_VAL_NRECS);
00802       } else {
00803         key.Clear();
00804 
00805         if(!previous_key.Empty()) {
00806           int is_first_change = 1;
00807 #if 0
00808           //
00809           // String
00810           //
00811           if(changes & WORD_CMPR_VAL_FLAGS_STRING) {
00812             unsigned int suffix_length = encoded->ShiftValue(WORD_CMPR_VAL_SUFFIX);
00813             unsigned int prefix_length = encoded->ShiftValue(WORD_CMPR_VAL_PREFIX);
00814             unsigned char* suffix = encoded->ShiftString(suffix_length);
00815             word.set(previous_key.GetWord().get(), prefix_length);
00816             word.append((const char*)suffix, suffix_length);
00817             key.SetWord(word);
00818             is_first_change = 0;
00819           } else {
00820             key.SetWord(previous_key.GetWord());
00821           }
00822 #endif
00823           //
00824           // Fields
00825           //
00826           int nfields = key.NFields();
00827           for(int j = 0; j < nfields; j++) {
00828             if(changes & WORD_CMPR_VAL_FLAGS_FIELD(j)) {
00829               unsigned int value = encoded->ShiftValue(j + WORD_CMPR_VAL_FIELDS);
00830               if(is_first_change) {
00831                 value += previous_key.Get(j);
00832                 is_first_change = 0;
00833               }
00834               key.Set(j, value);
00835             } else {
00836               key.Set(j, previous_key.Get(j));
00837             }
00838           }
00839           //
00840           // Structural data
00841           //
00842           if(changes & WORD_CMPR_VAL_FLAGS_PGNO) {
00843             bi.pgno = encoded->ShiftValue(WORD_CMPR_VAL_PGNO);
00844           } else {
00845             bi.pgno = previous_bi.pgno;
00846           }
00847           if(changes & WORD_CMPR_VAL_FLAGS_NRECS) {
00848             bi.nrecs = encoded->ShiftValue(WORD_CMPR_VAL_NRECS);
00849           } else {
00850             bi.nrecs = previous_bi.nrecs;
00851           }
00852         } else {
00853 #if 0
00854           //
00855           // String
00856           //
00857           unsigned int string_length = encoded->ShiftValue(WORD_CMPR_VAL_SUFFIX);
00858           unsigned char* string = encoded->ShiftString(string_length);
00859           key.SetWord((const char*)string, string_length);
00860 #endif
00861           //
00862           // Fields
00863           //
00864           int nfields = key.NFields();
00865           for(int i = 0; i < nfields; i++) {
00866             key.Set(i, encoded->ShiftValue(i + WORD_CMPR_VAL_FIELDS));
00867           }
00868           //
00869           // Structural data
00870           //
00871           bi.pgno = encoded->ShiftValue(WORD_CMPR_VAL_PGNO);
00872           bi.nrecs = encoded->ShiftValue(WORD_CMPR_VAL_NRECS);
00873         }
00874 
00875         key.Pack(packed);
00876       }
00877       data.data = packed.get();
00878       data.size = packed.length();
00879       bi.len = packed.length();
00880       bi.type = B_KEYDATA;
00881       
00882       //
00883       // Insert entry in page
00884       //
00885       cdb___db_pitem(pp, i, BINTERNAL_SIZE(bi.len), &hdr, &data);
00886 
00887       //
00888       // Current becomes previous
00889       //
00890       previous_bi = bi;
00891       previous_key = key;
00892     }
00893   }
00894 
00895   if(debug) {
00896     if(entry_count != NUM_ENT(pp))
00897       fprintf(stderr, "WordDBCompress::UncompressIBtree: pgno %d: NUM_ENT(pp) = %d is different than entry_count = %d\n", PGNO(pp), NUM_ENT(pp), entry_count);
00898   }
00899   return 0;
00900 }
00901 
00902 int 
00903 WordDBCompress::UncompressLBtree(const unsigned char *inbuff, int inbuff_length, unsigned char *outbuff, int outbuff_length)
00904 {
00905   memset((char*)outbuff, '\0', outbuff_length);
00906 
00907   WordBitCompress stream(inbuff_length);
00908   stream.BuffSet(inbuff + WORD_CMPR_OVERHEAD, inbuff_length - WORD_CMPR_OVERHEAD);
00909 
00910   PAGE *pp = (PAGE *)outbuff;
00911 
00912   TYPE_TAGS(pp) = *inbuff;
00913   HOFFSET(pp) = outbuff_length;
00914   short entry_count = 0;
00915   pp->lsn.file = stream.GetUint(sizeof(pp->lsn.file) * 8);
00916   pp->lsn.offset = stream.GetUint(sizeof(pp->lsn.offset) * 8);
00917   PGNO(pp) = stream.GetUint(sizeof(PGNO(pp)) * 8);
00918   PREV_PGNO(pp) = stream.GetUint(sizeof(PREV_PGNO(pp)) * 8);
00919   NEXT_PGNO(pp) = stream.GetUint(sizeof(NEXT_PGNO(pp)) * 8);
00920   entry_count = stream.GetUint(sizeof(NUM_ENT(pp)) * 8);
00921   LEVEL(pp) = stream.GetUint(sizeof(LEVEL(pp)) * 8);
00922 
00923   if(entry_count > 0) {
00924     int i;
00925     String packed;
00926     BKEYDATA bk;
00927     DBT hdr;
00928     hdr.data = &bk;
00929     hdr.size = SSZA(BKEYDATA, data);
00930     DBT data;
00931     WordKey key(context);
00932     WordKey previous_key(context);
00933     WordRecord record(context);
00934     WordRecord previous_record(context);
00935     int previous_record_p = 0;
00936     String word;
00937   
00938     encoded->Get(stream);
00939 
00940     for(i = 0; i < entry_count; i += P_INDX) {
00941       unsigned int changes = encoded->ShiftValue(WORD_CMPR_VAL_FLAGS);
00942 
00943       //
00944       // Key
00945       //
00946       {
00947         memset((char*)&bk, '\0', sizeof(BKEYDATA));
00948 
00949         key.Clear();
00950 
00951         if(!previous_key.Empty()) {
00952           int is_first_change = 1;
00953 #if 0
00954           //
00955           // String
00956           //
00957           if(changes & WORD_CMPR_VAL_FLAGS_STRING) {
00958             unsigned int suffix_length = encoded->ShiftValue(WORD_CMPR_VAL_SUFFIX);
00959             unsigned int prefix_length = encoded->ShiftValue(WORD_CMPR_VAL_PREFIX);
00960             unsigned char* suffix = encoded->ShiftString(suffix_length);
00961             word.set(previous_key.GetWord().get(), prefix_length);
00962             word.append((const char*)suffix, suffix_length);
00963             key.SetWord(word);
00964             is_first_change = 0;
00965           } else {
00966             key.SetWord(previous_key.GetWord());
00967           }
00968 #endif
00969           //
00970           // Fields
00971           //
00972           int nfields = key.NFields();
00973           for(int j = 0; j < nfields; j++) {
00974             if(changes & WORD_CMPR_VAL_FLAGS_FIELD(j)) {
00975               unsigned int value = encoded->ShiftValue(j + WORD_CMPR_VAL_FIELDS);
00976               if(is_first_change) {
00977                 value += previous_key.Get(j);
00978                 is_first_change = 0;
00979               }
00980               key.Set(j, value);
00981             } else {
00982               key.Set(j, previous_key.Get(j));
00983             }
00984           }
00985         } else {
00986 #if 0
00987           //
00988           // String
00989           //
00990           unsigned int string_length = encoded->ShiftValue(WORD_CMPR_VAL_SUFFIX);
00991           unsigned char* string = encoded->ShiftString(string_length);
00992           key.SetWord((const char*)string, string_length);
00993 #endif
00994           //
00995           // Fields
00996           //
00997           int nfields = key.NFields();
00998           for(int i = 0; i < nfields; i++) {
00999             key.Set(i, encoded->ShiftValue(i + WORD_CMPR_VAL_FIELDS));
01000           }
01001         }
01002 
01003         key.Pack(packed);
01004 
01005         data.data = packed.get();
01006         data.size = packed.length();
01007         bk.len = packed.length();
01008         bk.type = B_KEYDATA;
01009       
01010         //
01011         // Insert entry in page
01012         //
01013         cdb___db_pitem(pp, i, BKEYDATA_SIZE(bk.len), &hdr, &data);
01014 
01015         previous_key = key;
01016       }
01017       //
01018       // Record
01019       //
01020       {
01021         memset((char*)&bk, '\0', sizeof(BKEYDATA));
01022 
01023         if(changes & WORD_CMPR_VAL_FLAGS_RECORD_NO) {
01024           packed.trunc();
01025         } else {
01026           if(changes & WORD_CMPR_VAL_FLAGS_RECORD_STR) {
01027             record.type = WORD_RECORD_STR;
01028             if(previous_record_p &&
01029                (changes & WORD_CMPR_VAL_FLAGS_RECORD_EQ)) {
01030               record.info.str = previous_record.info.str;
01031             } else {
01032               unsigned int string_length = encoded->ShiftValue(WORD_CMPR_VAL_RLENGTH);
01033               unsigned char* string = encoded->ShiftString(string_length);
01034               record.info.str.set((const char*)string, string_length);
01035             }
01036           } else {
01037             record.type = WORD_RECORD_DATA;
01038           
01039             if(previous_record_p &&
01040                (changes & WORD_CMPR_VAL_FLAGS_RECORD_EQ)) {
01041               record.info.data = previous_record.info.data;
01042             } else {
01043               record.info.data = encoded->ShiftValue(WORD_CMPR_VAL_RVALUE);
01044             }
01045           }
01046 
01047           record.Pack(packed);
01048 
01049           previous_record = record;
01050           previous_record_p = 1;
01051         }
01052         data.data = packed.get();
01053         data.size = packed.length();
01054         bk.len = packed.length();
01055         bk.type = B_KEYDATA;
01056       
01057         //
01058         // Insert entry in page
01059         //
01060         cdb___db_pitem(pp, i + 1, BKEYDATA_SIZE(bk.len), &hdr, &data);
01061       }
01062     }
01063   }
01064 
01065   if(debug) {
01066     if(entry_count != NUM_ENT(pp))
01067       fprintf(stderr, "WordDBCompress::UncompressLBtree: pgno %d: NUM_ENT(pp) = %d is different than entry_count = %d\n", PGNO(pp), NUM_ENT(pp), entry_count);
01068   }
01069 
01070   return 0;
01071 }
01072 
01073 DB_CMPR_INFO* WordDBCompress::CmprInfo()
01074 {
01075   DB_CMPR_INFO *cmpr_info = new DB_CMPR_INFO;
01076 
01077   cmpr_info->user_data = (void *)this;
01078   cmpr_info->compress = WordDBCompress_compress_c;
01079   cmpr_info->uncompress = WordDBCompress_uncompress_c;
01080   cmpr_info->coefficient = 3;
01081   cmpr_info->max_npages = 9;
01082   cmprInfo = cmpr_info;
01083   
01084   return cmpr_info;
01085 }
01086 
01087 void
01088 WordDBCompress::DumpPage(const unsigned char* page) const
01089 {
01090   PAGE* pp = (PAGE*)page;
01091 
01092   fprintf(stderr, "--------------------------------------------------\n");
01093   fprintf(stderr, "pgno = %d ", PGNO(pp));
01094   fprintf(stderr, "lsn.file = %d ", pp->lsn.file);
01095   fprintf(stderr, "lsn.offset = %d ", pp->lsn.offset);
01096   fprintf(stderr, "prev_pgno = %d ", PREV_PGNO(pp));
01097   fprintf(stderr, "next_pgno = %d\n", NEXT_PGNO(pp));
01098   fprintf(stderr, "entries = %d ", NUM_ENT(pp));
01099   fprintf(stderr, "hf_offset = %d ", HOFFSET(pp));
01100   fprintf(stderr, "level = %d ", LEVEL(pp));
01101   fprintf(stderr, "type = %d\n", TYPE(pp));
01102   fprintf(stderr, "tags = 0x%02x\n", TAGS(pp));
01103   fprintf(stderr, "freespace = %d bytes from byte %d to byte %d\n", P_FREESPACE(pp), LOFFSET(pp), HOFFSET(pp));
01104 
01105   int i;
01106   for(i = 0; i < NUM_ENT(pp); i++) {
01107     fprintf(stderr, "index = %d, ", pp->inp[i]);
01108     unsigned char* data = 0;
01109     int data_length = 0;
01110     switch(TYPE(pp)) {
01111     case P_IBTREE:
01112       {
01113         BINTERNAL* e = GET_BINTERNAL(pp, i);
01114         fprintf(stderr, "len = %d, type = %d, pgno = %d, nrecs = %d\n", e->len, e->type, e->pgno, e->nrecs);
01115         data = (unsigned char*)e->data;
01116         data_length = e->len;
01117       }
01118       break;
01119     case P_LBTREE:
01120       {
01121         BKEYDATA* e = GET_BKEYDATA(pp, i);
01122         fprintf(stderr, "len = %d, type = %d\n", e->len, e->type);
01123         data = (unsigned char*)e->data;
01124         data_length = e->len;
01125       }
01126       break;
01127     }
01128     if(data_length > 0) {
01129       int j;
01130       for(j = 0; j < data_length; j++) {
01131         fprintf(stderr, "(%d) ", data[j]);
01132       }
01133       fprintf(stderr, "\n");
01134     }
01135   }
01136 }
01137 
01138 int
01139 WordDBCompress::DiffPage(const unsigned char* first, const unsigned char* second) const
01140 {
01141   PAGE* p1 = (PAGE*)first;
01142   PAGE* p2 = (PAGE*)second;
01143 
01144   if(TAGS(p1) != TAGS(p2)) return 1;
01145   if(TYPE(p1) != TYPE(p2)) return 1;
01146   if(PGNO(p1) != PGNO(p2)) return 1;
01147   if(p1->lsn.file != p2->lsn.file) return 1;
01148   if(p1->lsn.offset != p2->lsn.offset) return 1;
01149   if(TYPE(p1) == P_LBTREE) {
01150     if(PREV_PGNO(p1) != PREV_PGNO(p2)) return 1;
01151     if(NEXT_PGNO(p1) != NEXT_PGNO(p2)) return 1;
01152   }
01153   if(NUM_ENT(p1) != NUM_ENT(p2)) return 1;
01154   if(HOFFSET(p1) != HOFFSET(p2)) return 1;
01155   if(LEVEL(p1) != LEVEL(p2)) return 1;
01156 
01157   int i;
01158   for(i = 0; i < NUM_ENT(p1); i++) {
01159     unsigned char* data1 = 0;
01160     int data1_length = 0;
01161     unsigned char* data2 = 0;
01162     int data2_length = 0;
01163     switch(TYPE(p1)) {
01164     case P_IBTREE:
01165       {
01166         BINTERNAL* e1 = GET_BINTERNAL(p1, i);
01167         BINTERNAL* e2 = GET_BINTERNAL(p2, i);
01168         if(e1->len != e2->len) return 1;
01169         if(e1->type != e2->type) return 1;
01170         if(e1->pgno != e2->pgno) return 1;
01171         if(e1->nrecs != e2->nrecs) return 1;
01172         data1 = (unsigned char*)e1->data;
01173         data1_length = e1->len;
01174         data2 = (unsigned char*)e2->data;
01175         data2_length = e2->len;
01176       }
01177       break;
01178     case P_LBTREE:
01179       {
01180         BKEYDATA* e1 = GET_BKEYDATA(p1, i);
01181         BKEYDATA* e2 = GET_BKEYDATA(p2, i);
01182         if(e1->len != e2->len) return 1;
01183         if(e1->type != e2->type) return 1;
01184         data1 = (unsigned char*)e1->data;
01185         data1_length = e1->len;
01186         data2 = (unsigned char*)e2->data;
01187         data2_length = e2->len;
01188       }
01189       break;
01190     }
01191     if(data1_length > 0) {
01192       int j;
01193       for(j = 0; j < data1_length; j++) {
01194         if(data1[j] != data2[j]) return 1;
01195       }
01196     }
01197   }
01198   return 0;
01199 }

Generated on Sun Jun 8 10:56:40 2008 for GNUmifluz by  doxygen 1.5.5