fld.cpp

// MField - example field and Record classes, version 3
//
//	This version derives it's MRecord class from fstream, to provide
//  file reading and writing abilities
//
#include <fstream.h>
#include <assert.h>
#include <string.h>		// for memset and strlen
#include <stdio.h>		// for sprintf
#include <stdlib.h>		// for atoi
#include <ctype.h>		// for isdigit

// Our base class - MField
class MField
{
public:
	// Constructor and destructor
	MField( char *NewName=0, int NewSize=0, int NewOffset=0 );
	virtual ~MField() { delete [] Data;	delete [] Name; }

	MField( const MField& Src );
	MField& operator=(const MField&Src ) { assert( strcmp(GetType(),Src.GetType() )==0 );  memmove( Data, Src.Data, Size ); return(*this);}

	// Data getting and setting
	virtual char* GetAsString() const =0;	// Pure virtual
	virtual void SetFromString(char* Src) =0;	// Pure virtual

	// Basic interface routines
	char *GetName() { return(Name?Name:""); }
	int GetSize()	{ return( Size ); }
	void Clear()	{ memset( Data, 0, Size ); }

	// A routine to help identify data type
	virtual char * GetType() const = 0;	// Pure virtual function, MField is 'abstract'
	
protected:
	// Can be called only by this and derived classes
	void SetData( int NewSize );
	void SetName( char* NewName );
	friend class MRecord; // Version 3
	void* GetDataPntr() const { return( Data ); }

private:
	int Offset, Size;
	char * Data; // char * instead of void pointer, for simplicity
	char * Name;
};

// Constructor
MField::MField( char *NewName, int NewSize, int NewOffset )
{
	Name = 0;
	SetName(NewName);
	Data=0;
	SetData(NewSize);
	Offset = NewOffset;
	Clear();
}

// Copy constructor
MField::MField( const MField& Src )
{
	Name = 0;
	SetName(Src.Name);
	Data=0;
	SetData(Src.Size);
	Offset = Src.Offset;
	Clear();
}

void MField::SetData( int NewSize )		// Sets buffer size
{
	delete [] Data;
	Data = new char[ NewSize ];
	assert( Data != 0 );
	Size = NewSize;
}

void MField::SetName( char* NewName )	// Sets field name
{
	delete [] Name;
	Name = new char[ strlen( NewName?NewName:"" ) + 1 ];
	assert( Name!= 0 );
	strcpy( Name, NewName?NewName:"" );
}

////////////////////////////////////////////////////////////////////////
class MDateField : public MField
{
	typedef struct { short Y; char M, D; } MyDate;
public:
	MDateField( char* NewName=0, int Offset=0 ) : MField(NewName, sizeof(MyDate), Offset ) {}
	
	// Provide the functions that are pure virtual in our class:
	virtual char* GetAsString() const;
	virtual void SetFromString(char* Src);
	virtual char * GetType() const { return( "Date" ); }
};

// Returns a char pointer for the contents of the field
char* MDateField::GetAsString() const
{
	static char Perm[11];
	MyDate * Tmp = (MyDate*)GetDataPntr();
	sprintf( Perm, "%02d/%02d/%04d", Tmp->M, Tmp->D, Tmp->Y );
	return( Perm );
}

void MDateField::SetFromString(char* Src)
{
	MyDate * Tmp = (MyDate*)GetDataPntr();
	Tmp->M = Tmp->D = 0;
	Tmp->Y = 0;
	Tmp->M = atoi(Src);
	while( isdigit(*Src) )
		Src++;
	while( *Src && !isdigit( *Src ) )
		Src++;
	Tmp->D = atoi(Src);
	while( isdigit(*Src) )
		Src++;
	while( *Src && !isdigit( *Src ) )
		Src++;
	Tmp->Y = atoi(Src);
}
///////////////////////////////////////////////////////////////////////	
class MShortField : public MField
{
public:
	MShortField( char* NewName=0, int NewOffset=0 ) : MField(NewName, sizeof(short), NewOffset ) {}
	
	char* GetAsString() const;
	void SetFromString(char* Src);

	virtual char * GetType() const { return( "Short" ); }
};

char* MShortField::GetAsString() const
{
	static char Perm[7];
	sprintf( Perm, "%d", *(short*) GetDataPntr() );
	return( Perm );
}

void MShortField::SetFromString(char* Src)
{
	*(short*) GetDataPntr() = atoi(Src);
}
///////////////////////////////////////////////////////////////////////	
class MRecord : private fstream	// fstream base is new in version 3
{
public:
	
	MRecord() { FieldCount=RecNum=RecSize=0; }
	~MRecord() { while( FieldCount-- ) delete Fields[FieldCount];}

	// Stores a dynamically-allocate MField-type class
	void AddField( MField* ToAdd )
	{
		assert( FieldCount<(sizeof(Fields)/sizeof(Fields[0])) );
		Fields[ FieldCount++ ] = ToAdd;
		RecSize += ToAdd->GetSize();
	}
	// FindField - Finds a field index based on name
	int FindField( char* Name ) { for( int i=0; i < FieldCount; i++ ) 
		if( strcmp( Fields[i]->GetName(), Name )==0 ) 
			return( i ); 
		return(-1); 
	}
	// Removes an MField class added via AddField
	void RemoveField( char * Name ) { RemoveField( FindField(Name) ); }
	void RemoveField( int Index ) { assert( Index>=0 && Index<FieldCount);
		RecSize -= Fields[Index]->GetSize();
		delete Fields[Index];
		for( ; Index < FieldCount-1; Index++ )
			Fields[Index] = Fields[Index+1];
		FieldCount--;
	}
	//Gives direct access to a field vie index or name
	MField* Field( int Index ) { assert( Index>=0&&Index<FieldCount ); return(Fields[Index]);}
	MField* Field( char* Name ) { int i = FindField( Name );return( i==-1? 0 : Fields[i] );}
	// Blanks out all fields in the record
	void Clear() { for( int i=0; i < FieldCount; i++ ) Fields[i]->Clear(); }
	// Prompts user to populate all fields
	void Populate() 
	{ 
		for( int i=0; i < FieldCount; i++ )
		{
			if( Fields[i]->GetName()[0] == '[' )
				continue;
			char Input[128];

			cout << "Enter value for " << Fields[i]->GetName() << ":" << endl;
			cin >> Input;
			Fields[i]->SetFromString( Input );
		}
	}
	// Dumps all fields in record
	void Dump()
	{
		cout << "Record contents: " << endl;
		for( int i =0; i < FieldCount; i++ )
			cout << "  " << Fields[i]->GetName() << " -> " << Fields[i]->GetAsString() << endl;
	}
	int GetFieldCount() const { return( FieldCount ); }

	// New functions in version 3: Read, Write, Open, Close, and Goto
	int Read() { int Size=0;
		seekg( RecNum*RecSize); 
		for(int i=0; i < FieldCount; i++ ) { 
			read( (char*)Fields[i]->GetDataPntr(), Fields[i]->GetSize() ); 
			Size += gcount();
		}
		return( Size==RecSize );
	}
	int Write() { 
		seekp( RecNum*RecSize); 
		for(int i=0; i < FieldCount; i++ ) { 
			write( (char*)Fields[i]->GetDataPntr(), Fields[i]->GetSize() ); 
		}
		return( !(rdstate()&ios::failbit) );
	}
	
	int Open( char* Filename, int NoCreate=0 ) { Close(); open( Filename, ios::binary|ios::in|ios::out|(NoCreate?ios::nocreate:0)); return( !(rdstate()&ios::failbit) );}
	void Close() { if( is_open() ) close(); }
	void Goto( int nRecNum ) { RecNum = nRecNum; }
private:
	MField * Fields[128]; // The 128 should really be dynamic
	int FieldCount;
	int RecSize, RecNum;	// New data members in version 3

	// Note: For now, I disable you from doing assignment and copy, by making them 
	// private, and throwing an assert of you do execute them
	MRecord(const MRecord& Src ) { assert(0); };
	MRecord& operator=(const MRecord& Src ) { assert(0); return(*this);};

};

int Ask( char* Prompt, char* Accept )
{
	char Ch;
	do {
		cout << Prompt << endl;
		cin >> Ch;
		cin.ignore();
		Ch = toupper(Ch);
	} while( strchr( Accept, Ch ) == NULL );
	return(Ch);
}

void main()
{
	MRecord Rec;

	Rec.AddField( new MDateField("BDay") );
	Rec.AddField( new MShortField("ID") );
	Rec.AddField( new MDateField("HireDate") );
	
	Rec.Open( "test.dat" );
	if( Ask( "Read or Write record?", "RW" ) == 'W' )
	{
		Rec.Populate();
		Rec.Write();
	}
	else
	{
		Rec.Read();
		Rec.Dump();
	}

	Rec.Dump();
}