Link to: header | other data directory
Copyright Turtle Creek Software 1996-2006. All Rights Reserved.
Comments
CCustomFieldOwner
a mix-in class for persistent classes which want to support custom fields. Used by
DB_Account, CTransaction and CRentalUnit.
The approach we use for handling custom fields:
1. All custom field data is stored as a cstring.
2. when custom fields are added via the Layout Editor, we do NOT change any existing
records.
3. When we are actively using a record that includes custom fields, we store
them in CCustomDataHolders, and keep an internal array of pointers to them.
4. Newly created objects start out with a zero-element array. When a data object is edited,
during the SetMemberValue process we add array members as necessary. The
same thing will happen if re-editing an existing record that was created before
custom fields were added.
5. The custom data is then written to disk during WriteObject. Even if no data was
typed in, the record will simply have empty cstrings marking the custom fields.
6. On disk we store a count of fields, then a series of cstrings. We do
NOT tag the data- to save space, we just store them in order. Note that different
data records may have different counts, if custom fields were added after some
records were already created.
7. When an existing item that includes custom data is accessed, its data
is read into the object in ReadObject.
8. Export and import of custom field data happens automatically. Specs for the
custom fields are exported & imported via DB_ObjectClassInfo, so when importing
a company file with custom fields, the file is able to accept the incoming data.
Note that this setup means that it won't be easy to delete a custom field, and
currently we don't offer that option. To delete a field we'd have to immediately
loop through all records, check for presence of that data and remove it (moving all
subsequent fields down one).
This class manages custom fields for the Goldenseal accounting software,
project management software and construction project
estimating software.
RELATED CLASSES: DB_ClassDescriptor handles some access to custom fields.
DB_ObjectClassInfo is the persistent class that stores custom field info on disk.
Adding custom fields to a layout is handled by CLayoutEditor::DoAddCustomField
Source Code
/*********************************************************************************
CopyFrom rev TCS 1/2/03
copy data from another custom field owner
*********************************************************************************/
void CCustomFieldOwner::CopyFrom(const CCustomFieldOwner &source, const DBClass classID)
{
//mCustomFieldData = source.mCustomFieldData;
// first clear any existing items we may already have here
mCustomFieldData.RemoveAllItems();
TMemberArray customFieldArray;
SMemberInfo info;
DB_ClassDescriptor *desc = DB_ClassDescriptor::GetClassDescriptor(classID);
TCS_FailNILMsg(desc, TCS_GetErrString(errID_BadDescriptor));
desc->FillCustomFieldsArray(customFieldArray);
// we can't just copy the data array, since they'll both
// point to the same data and there will be crashes if
// we delete one owner, and then try to use the other.
// So we must create different holders and fill data individually.
if (source.mCustomFieldData.GetCount() > 0) // TCS bugfix 1/2/03
{
CCustomDataHolder *holder = nil,
*newHolder = nil;
CTextString aString;
TCustomDataArrayIterator iter(source.mCustomFieldData);
while (iter.Next(holder))
{
TCS_FailNILMsg(holder, TCS_GetErrString(errID_BadCustomField));
customFieldArray.FetchItemAt(iter.GetCurrentIndex(), info);
// copy the cstring, except for pictures (which can't both point
// to the same picture, hence we just zero out the new value) TCS rev 1/19/04
if (info.type == type_pictid)
aString = cZeroString;
else
aString = holder->GetCString();
// if we don't already have a data holder, add it
newHolder = NEW CCustomDataHolder(aString);
if (newHolder == nil)
{
// if we can't allocate a holder, let's
// free up some space and try again
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));
gDBFile->SaveAllAndPurge(aString.Length() * 50);
newHolder = NEW CCustomDataHolder(aString);
}
TCS_FailNILMsg(newHolder, TCS_GetErrString(errID_BadCustomField));
mCustomFieldData.Append(newHolder);
}
}
}
/*********************************************************************************
GetFileLength
return the amount of space taken up by the custom fields in the db
*********************************************************************************/
NeoSize CCustomFieldOwner::GetFileLength(const CNeoFormat */*aFormat*/) const
{
long length = cFileLength + sizeof(char); // we include an extra char for field count
if (mCustomFieldData.GetCount() > 0)
{ // there are some custom fields...walk through the fields and
// see how much space each one takes
TCustomDataArrayIterator iterator(mCustomFieldData);
CCustomDataHolder *holder;
while (iterator.Next(holder))
{
TCS_FailNILMsg(holder, TCS_GetErrString(errID_BadCustomField));
length += holder->GetFileLength();
}
}
return length;
}
/*********************************************************************************
GetCustomFieldValue (analog of GetMemberValue) TCS 6/3/99
return the value of one of the custom fields
*********************************************************************************/
Boolean CCustomFieldOwner::GetCustomFieldValue(const NeoTag aTag, const NeoTag aType,
void *aValue, const DBid classID) const
{
// let's check to see what custom fields are available. We used
// to get this from DB_ObjectClassInfo, but we now use the class
// descriptor and avoid an object call TCS rev 4/9/03
DB_ClassDescriptor *desc = DB_ClassDescriptor::GetClassDescriptor(classID);
TCS_FailNILMsg(desc, TCS_GetErrString(errID_BadDescriptor));
SInt32 fieldIndex = desc->GetCustomFieldIndex(aTag);
if (fieldIndex) // valid field, so fetch corresponding data
{
SMemberInfo memberInfo;
desc->FetchCustomFieldInfo(fieldIndex, memberInfo); // TCS 2/11/03
CCustomDataHolder *holder = nil;
mCustomFieldData.FetchItemAt(fieldIndex, holder);
if (holder)
{ // we have data, so let's fetch it
if (memberInfo.type == type_objectid) // TCS 2/11/03
{
DBid id = holder->GetValue();
return DB_PersistentObject::ConvertObjectIDMember(id, memberInfo.params, aValue, aType);
}
else
return holder->ConvertValueTo(aValue, aType);
}
else // no data, which probably means this is an
{ // older record that doesn't have data for this
// field yet. So just return an empty string
CTextString emptyString;
return DB_PersistentObject::ConvertMember(&emptyString, type_cstring, aValue, aType);
}
}
// if we got this far, we couldn't get a value
return false;
}
/*********************************************************************************
SetCustomFieldValue (analog of SetMemberValue) TCS 6/3/99
set the value of one of the custom fields
*********************************************************************************/
Boolean CCustomFieldOwner::SetCustomFieldValue(const NeoTag aTag, const NeoTag aType,
const void *aValue, const DBid classID)
{
// let's check to see what custom fields are available. We used
// to get this from DB_ObjectClassInfo, but we now use the class
// descriptor and avoid an object call TCS rev 4/9/03
DB_ClassDescriptor *desc = DB_ClassDescriptor::GetClassDescriptor(classID);
TCS_FailNILMsg(desc, TCS_GetErrString(errID_BadDescriptor));
SInt32 fieldIndex = desc->GetCustomFieldIndex(aTag);
if (fieldIndex) // valid field, so fill corresponding data
{
CCustomDataHolder *holder = nil;
// do we need to add some array elements?
while (mCustomFieldData.GetCount() < fieldIndex)
{
TCS_TRY // TCS 4/9/03
{
holder = NEW CCustomDataHolder();
}
TCS_CATCH {}
if (holder == nil)
{
// if we can't allocate a holder, let's
// free up some space and try again TCS 12/20/02
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));
gDBFile->SaveAllAndPurge(1000);
TCS_TRY
{
holder = NEW CCustomDataHolder();
}
TCS_CATCH {}
}
// if still no holder, we have serious memory problems.
// better to quit, rather than have half-ass data TCS rev 4/9/03
TCS_FailNILFatalMsg(holder, TCS_GetErrString(errID_BadCustomField));
// add the data
mCustomFieldData.Append(holder);
}
// fetch the data holder
mCustomFieldData.FetchItemAt(fieldIndex, holder);
// set data if available.
if (holder)
{
holder->ConvertValueFrom(aValue, aType);
}
return true;
}
else // not a valid custom field
return false;
}
/*********************************************************************************
ReadObject TCS 6/2/99
read the custom fields from the db stream
Note that we just fit stored data into the available custom fields. If
fields are deleted after data is entered for them, there is potential for
data ending up in the wrong field. We will probably prevent this by forbidding
the deletion of custom fields once data has been entered into them, since it
will be too difficult or slow to synchronize data for many objects
*********************************************************************************/
Boolean CCustomFieldOwner::ReadObject(CNeoStream &aStream, DB_PersistentObject *theObject)
{
if (!theObject)
return false;
// first read the count byte which tells us how many fields are being stored
// we need to know this, since any individual persistent object may not have
// data for all the potential custom fields(e.g. field were added after the
// object was created).
SInt32 numFields = aStream.ReadChar();
SInt32 classCustomFields = DB_ClassDescriptor::GetCustomFieldCount(theObject->GetDBClassID());
if (numFields > classCustomFields) // TCS 9/26/03
{
// if we are trying to read in more custom fields than the class
// actually contains, the data must be seriously corrupted. Note that
// we won't be able to make this test if we ever allow custom fields to
// be removed.
TCS_ErrorAlert(TCS_GetErrString(errID_BadCustomField));
return false;
}
else if (numFields)
{ // we have stored custom field data. Let's load it into our data array
// note that the count of read data fields may be less than the actual count
// for the class-- data may not have been added to this object yet
CTextString string;
CCustomDataHolder *holder = nil;
for (SInt32 fieldIndex = 1; fieldIndex <= numFields; fieldIndex++)
{
ReadTextFromStream(&aStream, &string);
if (mCustomFieldData.GetCount() < fieldIndex)
{
// if we don't already have a data holder, add it
holder = NEW CCustomDataHolder(string);
if (holder == nil)
{
// if we can't allocate a holder, let's
// free up some space and try again TCS 12/20/02
TCS_FailNILMsg(gDBFile, TCS_GetErrString(errID_BadFile));
gDBFile->SaveAllAndPurge(string.Length() * 50);
holder = NEW CCustomDataHolder(string);
}
TCS_FailNILMsg(holder, TCS_GetErrString(errID_BadCustomField));
mCustomFieldData.Append(holder);
}
else
{
// if we already have a data holder,
// reset its string value TCS 12/20/02
mCustomFieldData.FetchItemAt(fieldIndex, holder);
if (holder)
{
holder->SetCString(string);
}
else
{
holder = NEW CCustomDataHolder(string);
TCS_FailNILMsg(holder, TCS_GetErrString(errID_BadCustomField));
mCustomFieldData.Append(holder);
}
}
}
}
return true;
}
/*********************************************************************************
WriteObject TCS 6/3/99
write the custom fields to the db stream
*********************************************************************************/
Boolean CCustomFieldOwner::WriteObject(CNeoStream &aStream, DB_PersistentObject *theObject)
{
// first write the custom field count
UInt8 numFields = mCustomFieldData.GetCount();
SInt32 classCustomFields = DB_ClassDescriptor::GetCustomFieldCount(theObject->GetDBClassID());
if (numFields > classCustomFields) // TCS 9/26/03
{
// if we are trying to write more custom fields than the class
// actually contains, the data must be seriously corrupted. Note that
// we won't be able to make this test if we ever allow custom fields to
// be removed.
TCS_ErrorAlert(TCS_GetErrString(errID_BadCustomField));
return false;
}
// write the number of custom fields
aStream.WriteChar(numFields);
if (numFields)
{ // we have some fields to write
// walk through each field and write its data
CCustomDataHolder *holder = nil;
TCustomDataArrayIterator iterator(mCustomFieldData);
while (iterator.Next(holder))
{
TCS_FailNILMsg(holder, TCS_GetErrString(errID_BadCustomField));
holder->WriteData(aStream);
}
}
return true;
}
|