    // ------------------------------- //
    // -------- Start of File -------- //
    // ------------------------------- //
    // ----------------------------------------------------------- //
    // C++ Source Code File Name: grocery.cpp
    // Compiler Used: MSVC, BCC32, GCC, HPUX aCC, SOLARIS CC
    // Produced By: glNET Software
    // File Creation Date: 09/18/1997
    // Date Last Modified: 05/25/2001
    // Copyright (c) 2001 glNET Software
    // ----------------------------------------------------------- //
    // ------------- Program Description and Details ------------- //
    // ----------------------------------------------------------- //
    /*
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
    USA

    This is a test program use to test the Persistent base class.
    */
    // ----------------------------------------------------------- //
    #include "grocery.h"

    GroceryKey::GroceryKey() : DatabaseKeyB((char *)&key)
    {
      for(int i = 0; i < MAX_NAME_LENGTH; i++)
        key.object_name[i] = 0;
      key.object_id = (gxObjectID)0;
      key.class_id = (gxClassID)0;
    }

    GroceryKey::GroceryKey(const char *name, gxObjectID oid, gxClassID cid) :
      DatabaseKeyB((char *)&key)
    {
      strncpy(key.object_name, name,  MAX_NAME_LENGTH);
      key.object_name[MAX_NAME_LENGTH-1] = 0; // Ensure null termination
      key.object_id = oid;
      key.class_id = cid;
    }

    int GroceryKey::operator==(const DatabaseKeyB& k) const
    {
      const GroceryKey *kptr = (const GroceryKey *)(&k);
      int rv = strcmp(key.object_name, kptr->key.object_name);

      // NOTE: Duplicate names will not be allowed if the object ID is ignored
      return rv == 0;

      // Allow duplicate names by comparing the object ID
      // return ((rv == 0) && (key.object_id == kptr->key.object_id));
    }

    int GroceryKey::operator>(const DatabaseKeyB& k) const
    {
      const GroceryKey *kptr = (const GroceryKey *)(&k);
      int rv = strcmp(key.object_name, kptr->key.object_name);

      // NOTE: Duplicate names will not be allowed if the object ID is ignored
      return rv > 0;

      // Allow duplicate names by comparing the object ID
      // return ((rv > 0) && (key.object_id > kptr->key.object_id));
    }

    void GroceryKey::SetObjectName(const char *s)
    {
      strncpy(key.object_name, s,  MAX_NAME_LENGTH);
      key.object_name[MAX_NAME_LENGTH-1] = 0; // Ensure null termination
    }

    void Grocery::ClearName()
    // Clears the name string.
    {
      if(name) delete name; // Assumes the name string was allocated dynamically
      name = null_name;
    }

    __UWORD__ Grocery::ObjectLength()
    {
      return sizeof(stock_number) + sizeof(price) + StringFileLength(name);
    }

    gxDatabaseError Grocery::Write()
    {
      gxObjectHeader oh;

      // Allocate a block in the data file for this object's data
      objectaddress = pod->OpenDataFile()->Alloc(ObjectLength() +
    					     sizeof(gxObjectHeader));

      // Check for any allocation errors
      if(objectaddress == (FAU)0) {
        return pod->GetDataFileError();
      }

      oh.ClassID = GetClassID();
      oh.ObjectID = objectaddress;

      // Write the object header to the datafile
      if(WriteObjectHeader(oh)!= gxDBASE_NO_ERROR) {
        return pod->GetDataFileError();
      }

      // Write the item name to the data file
      if(WriteString(name) != gxDBASE_NO_ERROR) {
        return pod->GetDataFileError();
      }

      // Write the item's stock number to the data file
      if(pod->OpenDataFile()->Write(&stock_number, sizeof(stock_number)) !=
         gxDBASE_NO_ERROR) {
        return pod->GetDataFileError();
      }

      // Write the item's price to the data file
      if(pod->OpenDataFile()->Write(&price, sizeof(price)) != gxDBASE_NO_ERROR) {
        return pod->GetDataFileError();
      }

      // Write a 32-bit CRC checksum for the object
      // NOTE: This step should only be preformed if the application
      // requires the use of persistent checksum values.
      pod->OpenDataFile()->WriteObjectChecksum(objectaddress);
      if(pod->GetDataFileError() != gxDBASE_NO_ERROR) {
        return pod->GetDataFileError();
      }

      // Add the entry to the Index file
      if(UsingIndex()) {
        GroceryKey key((const char *)name, oh.ObjectID, oh.ClassID);
        GroceryKey compare_key;
        if(!AddKey(key, compare_key)) {
          return pod->GetIndexFileError();
        }
      }

      return gxDBASE_NO_ERROR;
    }

    gxDatabaseError Grocery::Read(FAU object_address)
    {
      gxObjectHeader oh;

      // Optimize seeks during intervening reads
      pod->OpenDataFile()->SeekTo(object_address);
      if(pod->GetDataFileError() != gxDBASE_NO_ERROR) {
        return pod->GetDataFileError();
      }

      if(ReadObjectHeader(oh, object_address) != gxDBASE_NO_ERROR) {
        return pod->GetDataFileError();
      }

      // Incorrect object type
      // NOTE: This step should only be preformed if class ID values
      // are required by the application.
      if(oh.ClassID != GetClassID()) {
        return pod->SetDataFileError(gxDBASE_BAD_CLASS_ID);
      }

      // Read the object's name and check for errors
      Name_t nbuf = (Name_t)ReadString();

      if(!nbuf) {
        return pod->GetDataFileError();
      }
      else {
        name = nbuf;
      }

      // Read the object's stock number
      if(pod->OpenDataFile()->Read(&stock_number, sizeof(stock_number)) !=
         gxDBASE_NO_ERROR) {
        return pod->GetDataFileError();
      }

      // Read the object's price
      if(pod->OpenDataFile()->Read(&price, sizeof(price)) != gxDBASE_NO_ERROR) {
        return pod->GetDataFileError();
      }

      objectaddress = object_address;

      return gxDBASE_NO_ERROR;
    }

    int Grocery::Find()
    {
      // Search the index file for this entry
      if(UsingIndex()) {
        GroceryKey key((const char *)this->name);
        GroceryKey compare_key;
        if(!FindKey(key, compare_key)) return 0;
        objectaddress = key.ObjectID();
        return 1; // Found the index file entry
      }

      // If not using index file search the data file
      Grocery grocery;
      FAU oa;      // Object Address
      gxBlockHeader gx;  // Block Header
      gxObjectHeader oh; // Object Header

      FAU gxdfileEOF = pod->OpenDataFile()->GetEOF();
      FAU addr = (FAU)0;
      addr = pod->OpenDataFile()->FindFirstBlock(addr); // Search the entire file

      if(addr == (FAU)0) return 0; // No database blocks found in file

      grocery.name = this->name;
      grocery.stock_number = this->stock_number;
      grocery.price = this->price;

      while(1) {
        if((addr + pod->OpenDataFile()->BlockHeaderSize()) >= gxdfileEOF) break;
        if(pod->OpenDataFile()->Read(&gx, sizeof(gxBlockHeader), addr) !=
           gxDBASE_NO_ERROR) {
          return 0;
        }
        if(gx.block_check_word == gxCheckWord) {
          if((__SBYTE__)gx.block_status == gxNormalBlock) {
    	oa = addr + pod->OpenDataFile()->BlockHeaderSize();
    	if(ReadObjectHeader(oh, oa) != gxDBASE_NO_ERROR) {
    	  return 0;
    	}
    	if(oh.ClassID == GetClassID()) {
    	  if(Read(oa) != gxDBASE_NO_ERROR) {
    	    return 0;
    	  }
    	  if(strcmp(name, grocery.name) == 0) {
    	    objectaddress = oa;
    	    return 1; // Found unique data member
    	  }
    	}
          }
          addr = addr + gx.block_length; // Goto the next database block
        }
        else {
          addr++; // Keep searching until a valid database block is found
        }
      }

      // Reset the objects data
      this->name = grocery.name;
      this->stock_number = grocery.stock_number;
      this->price = grocery.price;

      return 0; // Could not find
    }

    int Grocery::Delete()
    {
      if(UsingIndex()) {
        GroceryKey key((const char *)this->name);
        GroceryKey compare_key;
        if(!FindKey(key, compare_key)) return 0; // Could not find the key
        objectaddress = key.ObjectID();
        if(!DeleteObject(objectaddress)) return 0; // Could not delete the object
        if(!DeleteKey(key, compare_key)) return 0; // Could not delete the key
        return 1; // The index and data file entry was deleted
      }

      // If not using index file search the data file
      if(!Find()) return 0; // Could not find the data file entry
      if(DeleteObject(objectaddress)) return 0; // Could not delete the object

      // The object was deleted from the data file
      return 1;
    }

    int Grocery::CompareIndex(unsigned index_number)
    // Compares the data file to the index file.
    // Returns true if data and index file match.
    {
      if(!UsingIndex()) return 0;

      // Ensure that the in memory buffers and the file data
      // stay in sync during multiple file access.
      pod->Index(index_number)->TestTree();

      Grocery grocery(pod);
      GroceryKey key, compare_key;

      FAU oa;      // Object Address
      gxBlockHeader gx;  // Block Header
      gxObjectHeader oh; // Object Header

      int objects = 0; // Keeps track of good database blocks
      int matches = 0; // Keep track of matches

      FAU gxdfileEOF = pod->OpenDataFile()->GetEOF();
      FAU addr = (FAU)0;
      addr = pod->OpenDataFile()->FindFirstBlock(addr); // Search the entire file

      if(addr == (FAU)0) return 0; // No database blocks found in file

      while(1) {
        if((addr + pod->OpenDataFile()->BlockHeaderSize()) >= gxdfileEOF) break;
        pod->OpenDataFile()->Read(&gx, sizeof(gxBlockHeader), addr);
        if(gx.block_check_word == gxCheckWord) {
          if((__SBYTE__)gx.block_status == gxNormalBlock) {
    	oa = addr + pod->OpenDataFile()->BlockHeaderSize();
    	ReadObjectHeader(oh, oa);

    	if(oh.ClassID == GetClassID()) {
    	  objects++; // Increment the object count
    	  grocery.Read(oa);
    	  key.SetObjectName(grocery.name);
    	  key.SetObjectID(oa);
    	  key.SetClassID(oh.ClassID);
    	  if(FindKey(key, compare_key, index_number)) {
    	    matches++; // Index and data file match
    	  }
    	  grocery.ClearName();
    	}
          }
          addr = addr + gx.block_length; // Goto the next database block
        }
        else {
          addr++; // Keep searching until a valid database block is found
        }
      }

      return objects == matches;
    }

    int Grocery::RebuildIndexFile(const char *fname, unsigned index_number,
          			      int num_trees, BtreeNodeOrder_t node_order)
    {
      if(!UsingIndex()) return 0;

      GroceryKey key, compare_key;
      gxBtree btx(key, node_order);
      if(btx.Create(fname, num_trees) != gxDBASE_NO_ERROR) return 0;

      Grocery grocery(pod);

      FAU oa;            // Object Address
      gxBlockHeader gx;  // Block Header
      gxObjectHeader oh; // Object Header

      int objects = 0; // Keeps track of good database blocks
      int inserts = 0; // Keep track of inserts

      FAU gxdfileEOF = pod->OpenDataFile()->GetEOF();
      FAU addr = (FAU)0;
      addr = pod->OpenDataFile()->FindFirstBlock(addr); // Search the entire file

      if(addr == (FAU)0) return 0; // No database blocks found in file

      while(1) {
        if((addr + pod->OpenDataFile()->BlockHeaderSize()) >= gxdfileEOF) break;
        pod->OpenDataFile()->Read(&gx, sizeof(gxBlockHeader), addr);
        if(gx.block_check_word == gxCheckWord) {
          if((__SBYTE__)gx.block_status == gxNormalBlock) {
    	oa = addr + pod->OpenDataFile()->BlockHeaderSize();
    	ReadObjectHeader(oh, oa);
    	if(oh.ClassID == GetClassID()) {
    	  objects++; // Increment the object count
    	  grocery.Read(oa);
    	  key.SetObjectName(grocery.name);
    	  key.SetObjectID(oa);
    	  key.SetClassID(oh.ClassID);

    	  grocery.ClearName(); // Prevent any memory leaks

    	  // Could not add the key
    	  if(!btx.Insert(key, compare_key)) return 0;
    	  inserts++; // Index and data file match
    	}
          }
          addr = addr + gx.block_length; // Goto the next database block
        }
        else {
          addr++; // Keep searching until a valid database block is found
        }
      }

      return objects == inserts;
    }
    // ----------------------------------------------------------- //
    // ------------------------------- //
    // --------- End of File --------- //
    // ------------------------------- //