/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ /* file: abthumb.cpp ** Some portions derive from public domain IronDoc code and interfaces. ** ** Changes: ** <0> 09Mar1998 first implementation */ #ifndef _ABTABLE_ #include "abtable.h" #endif #ifndef _ABMODEL_ #include "abmodel.h" #endif /*3456789_123456789_123456789_123456789_123456789_123456789_123456789_12345678*/ /* ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- */ #if AB_CONFIG_TRACE_orDEBUG_orPRINT static const char* ab_Thumb_kClassName /*i*/ = "ab_Thumb"; #endif /* end AB_CONFIG_TRACE_orDEBUG_orPRINT*/ #if AB_CONFIG_TRACE_orDEBUG_orPRINT static const char* AB_Thumb_kClassName /*i*/ = "AB_Thumb"; #endif /* end AB_CONFIG_TRACE_orDEBUG_orPRINT*/ /*============================================================================= * ab_Thumb: import or export position */ // ````` ````` ````` ````` ````` ````` ````` ````` // virtual ab_Object methods char* ab_Thumb::ObjectAsString(ab_Env* ev, char* outXmlBuf) const /*i*/ { AB_USED_PARAMS_1(ev); #if AB_CONFIG_TRACE_orDEBUG_orPRINT const char* conflictsName = mThumb_ConflictReportFileName; if ( !conflictsName ) conflictsName = ""; sprintf(outXmlBuf, "", (long) this, // me=\"^%lX\" (long) mThumb_ListPosMap, // map=\"^%lX\" (long) mThumb_FilePos, // fp=\"#%lX\" (long) mThumb_RowPos, // rp=\"%lu\" (long) mThumb_RowCountLimit, // rl=\"%lu\" (long) mThumb_ByteCountLimit, // bl=\"%lu\" conflictsName, // cf=\"%.16s (long) mThumb_ReportFile, // /^%lX\" (const char*) &mThumb_FileFormat, // form=\"%.4s\" (unsigned long) mObject_RefCount, // rc=\"%lu\" this->GetObjectAccessAsString(), // ac=\"%.9s\" this->GetObjectUsageAsString() // us=\"%.9s\" ); #else *outXmlBuf = 0; /* empty string */ #endif /*AB_CONFIG_TRACE_orDEBUG_orPRINT*/ return outXmlBuf; } void ab_Thumb::CloseObject(ab_Env* ev) /*i*/ { if ( this->IsOpenObject() ) { this->MarkClosing(); this->CloseThumb(ev); this->MarkShut(); } } void ab_Thumb::PrintObject(ab_Env* ev, ab_Printer* ioPrinter) const /*i*/ { #ifdef AB_CONFIG_PRINT ioPrinter->PutString(ev, ""); char xmlBuf[ ab_Object_kXmlBufSize + 2 ]; if ( this->IsOpenObject() ) { ioPrinter->PushDepth(ev); // indent all objects in the list ioPrinter->NewlineIndent(ev, /*count*/ 1); ioPrinter->PutString(ev, this->ObjectAsString(ev, xmlBuf)); if ( mThumb_ListPosMap ) { ioPrinter->NewlineIndent(ev, /*count*/ 1); mThumb_ListPosMap->PrintObject(ev, ioPrinter); } if ( mThumb_ReportFile ) { ioPrinter->NewlineIndent(ev, /*count*/ 1); mThumb_ReportFile->PrintObject(ev, ioPrinter); } ioPrinter->PopDepth(ev); // stop indentation } else // use ab_Object::ObjectAsString() for non-objects: { ioPrinter->PutString(ev, this->ab_Object::ObjectAsString(ev, xmlBuf)); } ioPrinter->NewlineIndent(ev, /*count*/ 1); ioPrinter->PutString(ev, ""); #else /*AB_CONFIG_PRINT*/ AB_USED_PARAMS_2(ev, ioPrinter); #endif /*AB_CONFIG_PRINT*/ } ab_Thumb::~ab_Thumb() /*i*/ { AB_ASSERT(mThumb_ListPosMap==0); AB_ASSERT(mThumb_ReportFile==0); AB_ASSERT(mThumb_ConflictReportFileName==0); #if AB_CONFIG_TRACE_orDEBUG_orPRINT ab_Object* obj = mThumb_ListPosMap; if ( obj ) obj->ObjectNotReleasedPanic(ab_Thumb_kClassName); obj = mThumb_ReportFile; if ( obj ) obj->ObjectNotReleasedPanic(ab_Thumb_kClassName); #endif /*AB_CONFIG_TRACE_orDEBUG_orPRINT*/ } // ````` ````` ````` ````` ````` ````` ````` ````` // non-poly ab_Thumb methods ab_Thumb::ab_Thumb(ab_Env* ev, const ab_Usage& inUsage) /*i*/ : ab_Object(inUsage) , mThumb_ListPosMap( 0 ) , mThumb_ImportConflictPolicy( 0 ) , mThumb_ConflictReportFileName( 0 ) , mThumb_ReportFile( 0 ) , mThumb_FilePos( 0 ) , mThumb_RowPos( 0 ) , mThumb_RowCountLimit( 0xFFFFFFFF ) // very large ("all the rows") , mThumb_ByteCountLimit( 0xFFFFFFFF ) // very large ("all the file bytes") , mThumb_ImportFileLength( 0 ) // total file bytes to be imported , mThumb_ExportTotalRowCount( 0 ) // total rows to be exported , mThumb_CurrentPass( 0 ) // have not started first pass yet , mThumb_FileFormat( ab_File_kLdifFormat ) , mThumb_ListCountInFirstPass( 0 ) , mThumb_ListCountInSecondPass( 0 ) , mThumb_PortingCallCount( 0 ) // times ImportLdif() or ExportLdif() is called , mThumb_HavePreparedSecondPass( AB_kFalse ) { AB_USED_PARAMS_1(ev); } static const char* ab_Thumb_kDefaultConflictReportFileName = "ab.import.conflicts"; void ab_Thumb::determine_import_conflict_policy(ab_Env* ev) /*i*/ { ab_Env_BeginMethod(ev, ab_Thumb_kClassName, "determine_import_conflict_policy") if ( !mThumb_ConflictReportFileName ) { ab_policy policy = (ab_policy) AB_Policy_ImportConflicts_kDefault; char nameBuf[ AB_Env_kConflictReportFileNameSize ]; const char* name = ab_Thumb_kDefaultConflictReportFileName; AB_Env* cev = ev->AsSelf(); AB_Env_mImportConflictPolicy fun = cev->sEnv_ImportConflictPolicy; if ( fun ) { policy = (*fun)(cev, nameBuf); nameBuf[ AB_Env_kConflictReportFileNameSize - 1 ] = 0; // force end if ( ev->Good() ) name = nameBuf; if ( policy > (ab_policy) AB_Policy_ImportConflicts_kMax ) policy = (ab_policy) AB_Policy_ImportConflicts_kDefault; } mThumb_ImportConflictPolicy = policy; mThumb_ConflictReportFileName = ev->CopyString(name); } ab_Env_EndMethod(ev) } ab_policy ab_Thumb::GetImportConflictPolicy(ab_Env* ev) /*i*/ { ab_policy outPolicy = mThumb_ImportConflictPolicy; if ( !outPolicy ) { ab_Env_BeginMethod(ev, ab_Thumb_kClassName, "GetImportConflictPolicy") this->determine_import_conflict_policy(ev); outPolicy = mThumb_ImportConflictPolicy; if ( !outPolicy ) outPolicy = AB_Policy_ImportConflicts_kDefault; ab_Env_EndMethod(ev) } return outPolicy; } const char* ab_Thumb::GetConflictReportFileName(ab_Env* ev) /*i*/ { const char* outName = mThumb_ConflictReportFileName; if ( !outName ) { ab_Env_BeginMethod(ev, ab_Thumb_kClassName, "GetConflictReportFileName") this->determine_import_conflict_policy(ev); outName = mThumb_ConflictReportFileName; if ( !outName ) outName = ab_Thumb_kDefaultConflictReportFileName; ab_Env_EndMethod(ev) } return outName; } ab_StdioFile* ab_Thumb::GetReportFile(ab_Env* ev) /*i*/ { ab_StdioFile* outFile = mThumb_ReportFile; if ( !outFile ) { ab_Env_BeginMethod(ev, ab_Thumb_kClassName, "GetReportFile") const char* fileName = this->GetConflictReportFileName(ev); if ( fileName && ev->Good() ) { ab_StdioFile* file = new(*ev) ab_StdioFile(ev, ab_Usage::kHeap, fileName, "a+"); if ( file ) { if ( ev->Good() ) mThumb_ReportFile = file; else file->ReleaseObject(ev); } } outFile = mThumb_ReportFile; ab_Env_EndMethod(ev) } return outFile; } ab_IntMap* ab_Thumb::GetListPosMap(ab_Env* ev) /*i*/ // Get the hash table that maps list uids to file positions. // This method creates this object lazily on the first request, and // then keeps the map until the thumb is closed. If one wishes to // know whether there is any content without forcing this table to // exist, one can call GetMappedListUidCount() to get the number of // lists that have been mapped, which can avoid creating the map. { ab_IntMap* outIntMap = mThumb_ListPosMap; if ( !outIntMap ) { ab_Env_BeginMethod(ev, ab_Thumb_kClassName, "GetListPosMap") ab_IntMap* map = new(*ev) ab_IntMap(ev, ab_Usage::kHeap, /*inKeyMethods*/ 0, ab_IntMap_kMailingListImportHint); if ( map ) { if ( ev->Good() ) mThumb_ListPosMap = outIntMap = map; else map->ReleaseObject(ev); } ab_Env_EndMethod(ev) } return outIntMap; } ab_num ab_Thumb::PickHeuristicStreamBufferSize() const /*i*/ // Typically returns ab_Thumb_kNormalSteamBufSize except when values // of either (or both) mThumb_RowCountLimit and mThumb_ByteCountLimit // imply substantially more or less buffer space might make more sense. // Bounded by kMin and kMax values defined near kNormalSteamBufSize. // For sizing purposes, average row size is assumed kAverageRowSize. { ab_num outSize = ab_Thumb_kNormalSteamBufSize; // default value // pick larger of expected bytes or expected row record size ab_num expected = mThumb_ByteCountLimit; ab_num rows = mThumb_RowCountLimit; if ( rows < ab_Thumb_kMaxSteamBufSize ) // multiply if not already huge rows *= ab_Thumb_kAverageRowSize; if ( rows > expected ) expected = rows; if ( expected < outSize ) // seem to need less buffer space? { ab_num diff = outSize - expected; if ( diff > 1024 ) // difference seems sizeable? { outSize = expected + 1024; // allow for a bit extra if ( outSize < ab_Thumb_kMinSteamBufSize ) // below min? outSize = ab_Thumb_kMinSteamBufSize; // no less than this } } else if ( expected > outSize ) // seem to need more buffer space? { if ( expected > ab_Thumb_kMaxSteamBufSize ) outSize = ab_Thumb_kMaxSteamBufSize; // no more than this else { outSize = expected + 1024; // allow for a bit extra if ( outSize > ab_Thumb_kMaxSteamBufSize ) // above max? outSize = ab_Thumb_kMaxSteamBufSize; // no more than this } } return outSize; } void ab_Thumb::CloseThumb(ab_Env* ev) /*i*/ { ab_Env_BeginMethod(ev, ab_Thumb_kClassName, "CloseThumb") ab_Object* obj = mThumb_ListPosMap; if ( obj ) { mThumb_ListPosMap = 0; obj->ReleaseObject(ev); } obj = mThumb_ReportFile; if ( obj ) { if ( mThumb_ReportFile->IsOpenAndActiveFile() ) mThumb_ReportFile->Flush(ev); mThumb_ReportFile->CloseStdio(ev); mThumb_ReportFile = 0; obj->ReleaseObject(ev); } char* name = mThumb_ConflictReportFileName; if ( name ) { mThumb_ConflictReportFileName = 0; ev->FreeString(name); } ab_Env_EndMethod(ev) } /*============================================================================= * ab_IntMap: collection of assocs from key uid to value uid */ /*3456789_123456789_123456789_123456789_123456789_123456789_123456789_12345678*/ /* ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- */ #if AB_CONFIG_TRACE_orDEBUG_orPRINT static const char* ab_IntMap_kClassName /*i*/ = "ab_IntMap"; #endif /* end AB_CONFIG_TRACE_orDEBUG_orPRINT*/ #if AB_CONFIG_TRACE_orDEBUG_orPRINT static const char* ab_IntMapIter_kClassName /*i*/ = "ab_IntMapIter"; #endif /* end AB_CONFIG_TRACE_orDEBUG_orPRINT*/ // ````` ````` ````` ````` ````` ````` ````` ````` // public: // virtual ab_Object methods char* ab_IntMap::ObjectAsString(ab_Env* ev, char* outXmlBuf) const /*i*/ { AB_USED_PARAMS_1(ev); #if AB_CONFIG_TRACE_orDEBUG_orPRINT XP_SPRINTF(outXmlBuf, "", (long) this, // me=\"^%lX\" (long) mIntMap_ChangeCount, // chg=\"%lu\" (long) mIntMap_Capacity, // cap:thr=\"%lu (long) mIntMap_Threshold, // :%lu\" (long) mIntMap_AssocCount, // cnt=\"%lu\" (long) mIntMap_KeyInts, // k:v=\"^%lX (long) mIntMap_ValueInts, // :^%lX\" (unsigned long) mObject_RefCount, // rc=\"%lu\" this->GetObjectAccessAsString(), // ac=\"%.9s\" this->GetObjectUsageAsString() // us=\"%.9s\" ); #else *outXmlBuf = 0; /* empty string */ #endif /*AB_CONFIG_TRACE_orDEBUG_orPRINT*/ return outXmlBuf; } void ab_IntMap::CloseObject(ab_Env* ev) /*i*/ { if ( this->IsOpenObject() ) { this->MarkClosing(); this->CloseIntMap(ev); this->MarkShut(); } } void ab_IntMap::PrintObject(ab_Env* ev, ab_Printer* ioPrinter) const /*i*/ { #ifdef AB_CONFIG_PRINT ioPrinter->PutString(ev, ""); char xmlBuf[ ab_Object_kXmlBufSize + 2 ]; if ( this->IsOpenObject() ) { ioPrinter->NewlineIndent(ev, /*count*/ 1); ioPrinter->PutString(ev, this->ObjectAsString(ev, xmlBuf)); ab_map_int* keys = mIntMap_KeyInts; if ( keys ) // any associations? { ab_map_int* end = keys + mIntMap_Capacity; // one past last bucket --keys; // prepare for preincrement ioPrinter->PushDepth(ev); // indent all buckets ab_error_count ec = ev->ErrorCount(); while ( ++keys < end && ec <= ev->ErrorCount() ) { ab_map_int k = *keys; if ( k ) // is this bucket in use? { ab_u4 actual = keys - mIntMap_KeyInts; ab_u4 hash = this->hash_bucket(ev, k); if ( mIntMap_ValueInts ) // print the value too? { ab_map_int v = mIntMap_ValueInts[ actual ]; (*mIntMap_AssocString)(ev, k, v, xmlBuf, hash, actual); //XP_SPRINTF(xmlBuf, //"%lu\"/>", // (long) k, // pair=\"#%lX // (long) v, // :#%lX\" // (long) hashBucket, // bucket=\"%lu // (long) actualBucket // ->%lu\" //); } else // each bucket contains only a key { ab_map_int v = ab_IntMap_kPretendValue; (*mIntMap_AssocString)(ev, k, v, xmlBuf, hash, actual); //XP_SPRINTF(xmlBuf, //"%lu\"/>", // (long) k, // key=\"#%lX\" // (long) hashBucket, // bucket=\"%lu // (long) actualBucket // ->%lu\" //); } ioPrinter->NewlineIndent(ev, /*count*/ 1); ioPrinter->PutString(ev, xmlBuf); } } ioPrinter->PopDepth(ev); // stop bucket indentation } } else // use ab_Object::ObjectAsString() for non-objects: { ioPrinter->PutString(ev, this->ab_Object::ObjectAsString(ev, xmlBuf)); } ioPrinter->NewlineIndent(ev, /*count*/ 1); ioPrinter->PutString(ev, ""); #else /*AB_CONFIG_PRINT*/ AB_USED_PARAMS_2(ev,ioPrinter); #endif /*AB_CONFIG_PRINT*/ } ab_IntMap::~ab_IntMap() /*i*/ { AB_ASSERT(mIntMap_KeyInts==0); AB_ASSERT(mIntMap_ValueInts==0); } // ````` ````` ````` ````` ````` ````` ````` ````` // private: // private non-poly ab_IntMap helper methods void ab_IntMap::grow_int_map(ab_Env* ev) /*i*/ // increase capacity by some percentage { ab_Env_BeginMethod(ev, ab_IntMap_kClassName, "grow_int_map") if ( mIntMap_AssocCount >= mIntMap_Threshold ) { ab_num oldThreshold = mIntMap_Threshold; // save before we modify ab_num newSizeHint = ( mIntMap_Threshold * 4 ) / 3; // 33% growth mIntMap_Threshold = newSizeHint; // temporarily bigger during copy: ab_IntMap biggerCopy(ev, ab_Usage::kStack, newSizeHint, *this); mIntMap_Threshold = oldThreshold; // restore original value #ifdef AB_CONFIG_TRACE if ( ev->DoTrace() ) { this->TraceObject(ev); biggerCopy.TraceObject(ev); } #endif /*AB_CONFIG_TRACE*/ if ( ev->Good() && biggerCopy.mIntMap_KeyInts ) // successful growth? { // swap vital statistics and storage, to become the biggerCopy ab_map_int* oldKeys = mIntMap_KeyInts; // save for swap ab_map_int* oldValues = mIntMap_ValueInts; // save for swap mIntMap_KeyInts = biggerCopy.mIntMap_KeyInts; // transfer mIntMap_ValueInts = biggerCopy.mIntMap_ValueInts; // transfer biggerCopy.mIntMap_KeyInts = oldKeys; // to be freed biggerCopy.mIntMap_ValueInts = oldValues; // to be freed mIntMap_ChangeCount++; // note change (don't copy from biggerCopy) mIntMap_Threshold = biggerCopy.mIntMap_Threshold; mIntMap_Capacity = biggerCopy.mIntMap_Capacity; #ifdef AB_CONFIG_DEBUG if ( mIntMap_AssocCount != biggerCopy.mIntMap_AssocCount ) // drift?! { ev->Break( "", (long) mIntMap_AssocCount, (long) biggerCopy.mIntMap_AssocCount); this->BreakObject(ev); } #endif /*AB_CONFIG_DEBUG*/ mIntMap_AssocCount = biggerCopy.mIntMap_AssocCount; } biggerCopy.CloseIntMap(ev); // deallocate vectors in this map } ab_Env_EndMethod(ev) } ab_bool ab_IntMap::init_with_size(ab_Env* ev, const ab_KeyMethods* inKeyMethods, ab_num inSizeHint) /*i*/ { ab_Env_BeginMethod(ev, ab_IntMap_kClassName, "init_with_size") if ( inKeyMethods ) { mIntMap_Hash = inKeyMethods->mKeyMethods_Hash; mIntMap_Equal = inKeyMethods->mKeyMethods_Equal; mIntMap_AssocString = inKeyMethods->mKeyMethods_AssocString; } if ( mIntMap_KeyInts ) { ev->HeapFree(mIntMap_KeyInts); mIntMap_KeyInts = 0; } if ( mIntMap_ValueInts ) { mIntMap_HasValuesToo = AB_kTrue; ev->HeapFree(mIntMap_ValueInts); mIntMap_ValueInts = 0; } mIntMap_AssocCount = 0; mIntMap_Capacity = 0; mIntMap_Threshold = 0; if ( ev->Good() ) // proceed to allocate key and value int arrays? { ab_num capacity = (inSizeHint * 5) / 4; // one quarter more (25%) mIntMap_Threshold = inSizeHint; mIntMap_Capacity = capacity; ab_num volume = capacity * sizeof(ab_map_int); // bytes in each array mIntMap_KeyInts = (ab_map_int*) ev->HeapAlloc(volume); if ( mIntMap_KeyInts ) // allocated keys? { AB_MEMSET(mIntMap_KeyInts, 0, volume); // fill with all zeroes if ( mIntMap_HasValuesToo ) // need to allocate values vector? { mIntMap_ValueInts = (ab_map_int*) ev->HeapAlloc(volume); if ( mIntMap_ValueInts ) // allocated values? AB_MEMSET(mIntMap_ValueInts, 0, volume); // zero all } } } ab_Env_EndMethod(ev) return ev->Good(); } // ````` ````` ````` ````` ````` ````` ````` ````` // public: // non-poly ab_IntMap methods ab_IntMap::ab_IntMap(ab_Env* ev, const ab_Usage& inUsage, /*i*/ const ab_KeyMethods* inKeyMethods, ab_num inSizeHint) : ab_Object(inUsage), mIntMap_ChangeCount( 0 ), mIntMap_Threshold( 0 ), mIntMap_Capacity( 0 ), mIntMap_AssocCount( 0 ), mIntMap_KeyInts( 0 ), mIntMap_ValueInts( 0 ), mIntMap_Hash( 0 ), mIntMap_Equal( 0 ), mIntMap_AssocString( 0 ), mIntMap_HasValuesToo( AB_kFalse ) // defaults to supporting values in addition to keys. inSizeHint // is capped between kMinSizeHin and kMaxSizeHint, and this becomes // the new threshold, with capacity equal to 25% more. { if ( inSizeHint < ab_IntMap_kMinSizeHint ) inSizeHint = ab_IntMap_kMinSizeHint; else if ( inSizeHint > ab_IntMap_kMaxSizeHint ) inSizeHint = ab_IntMap_kMaxSizeHint; if ( inKeyMethods ) this->init_with_size(ev, inKeyMethods, inSizeHint); else { ab_KeyMethods defaults(ab_Env_HashKey, ab_Env_EqualKey, ab_Env_AssocString); this->init_with_size(ev, &defaults, inSizeHint); } } ab_IntMap::ab_IntMap(ab_Env* ev, const ab_Usage& inUsage, /*i*/ const ab_KeyMethods* inKeyMethods, ab_num inSizeHint, ab_bool inValuesToo) : ab_Object(inUsage), mIntMap_ChangeCount( 0 ), mIntMap_Threshold( 0 ), mIntMap_Capacity( 0 ), mIntMap_AssocCount( 0 ), mIntMap_KeyInts( 0 ), mIntMap_ValueInts( 0 ), mIntMap_Hash( 0 ), mIntMap_Equal( 0 ), mIntMap_AssocString( 0 ), mIntMap_HasValuesToo( inValuesToo ) // map contains no values when inValuesToo is false, which technically // creates a simple set and not a map. inSizeHint // is capped between kMinSizeHin and kMaxSizeHint, and this becomes // the new threshold, with capacity equal to 25% more. { if ( inSizeHint < ab_IntMap_kMinSizeHint ) inSizeHint = ab_IntMap_kMinSizeHint; else if ( inSizeHint > ab_IntMap_kMaxSizeHint ) inSizeHint = ab_IntMap_kMaxSizeHint; if ( inKeyMethods ) this->init_with_size(ev, inKeyMethods, inSizeHint); else { ab_KeyMethods defaults(ab_Env_HashKey, ab_Env_EqualKey, ab_Env_AssocString); this->init_with_size(ev, &defaults, inSizeHint); } } ab_IntMap::ab_IntMap(ab_Env* ev, const ab_Usage& inUsage, /*i*/ ab_num inSizeHint, const ab_IntMap& ioOtherMap) : ab_Object(inUsage), mIntMap_ChangeCount( 0 ), mIntMap_Threshold( 0 ), mIntMap_Capacity( 0 ), mIntMap_AssocCount( 0 ), mIntMap_KeyInts( 0 ), mIntMap_ValueInts( 0 ), mIntMap_Hash( ioOtherMap.mIntMap_Hash ), mIntMap_Equal( ioOtherMap.mIntMap_Equal ), mIntMap_AssocString( ioOtherMap.mIntMap_AssocString ), mIntMap_HasValuesToo( ioOtherMap.mIntMap_ValueInts != 0 ) // The new map contains values provided ioOtherMap does. inSizeHint is // capped between kMinSizeHin and kMaxSizeHint, but is then increased // to ioOtherMap.GetThreshold() is this is greater, and then used for // the new threshold, with capacity equal to 25% more. The content in // ioOtherMap is copied into this one. (This constructor is used when // a map is grown with grow_int_map(), which makes a larger local map // before replacing the old slots when this is successful.) { if ( inSizeHint < ab_IntMap_kMinSizeHint ) inSizeHint = ab_IntMap_kMinSizeHint; else if ( inSizeHint > ab_IntMap_kMaxSizeHint ) inSizeHint = ab_IntMap_kMaxSizeHint; if ( inSizeHint < ioOtherMap.GetThreshold() ) inSizeHint = ioOtherMap.GetThreshold(); if ( this->init_with_size(ev, /*inKeyMethods*/ 0, inSizeHint) ) { this->AddTable(ev, ioOtherMap); } } void ab_IntMap::CloseIntMap(ab_Env* ev) /*i*/ { ab_Env_BeginMethod(ev, ab_IntMap_kClassName, "CloseIntMap") if ( mIntMap_KeyInts ) { ev->HeapFree(mIntMap_KeyInts); mIntMap_KeyInts = 0; } if ( mIntMap_ValueInts ) { ev->HeapFree(mIntMap_ValueInts); mIntMap_ValueInts = 0; } ab_Env_EndMethod(ev) } // ````` ````` entire table methods ````` ````` ab_bool ab_IntMap::AddTable(ab_Env* ev, const ab_IntMap& ioOtherMap) /*i*/ // add contents of ioOtherMap, and then return ev->Good(). { ab_Env_BeginMethod(ev, ab_IntMap_kClassName, "AddTable") if ( this->IsOpenObject() && ioOtherMap.IsOpenObject() ) { if ( this != &ioOtherMap && ev->Good() ) { ab_map_int* keys = ioOtherMap.mIntMap_KeyInts; ab_map_int* end = keys + ioOtherMap.mIntMap_Capacity; ab_map_int* cursor = --keys; // prepare for preincrement ab_map_int* values = ioOtherMap.mIntMap_ValueInts; ++mIntMap_ChangeCount; // note change in map (fast_add() does not) while ( ++cursor < end && ev->Good() ) { ab_map_int k = *cursor; if ( k ) // another key in the other table? { if ( values ) this->fast_add(ev, k, values[ cursor - keys ]); else this->fast_add(ev, k, ab_IntMap_kPretendValue); } } } } else ev->NewAbookFault(ab_Object_kFaultNotOpen); ab_Env_EndMethod(ev) return ev->Good(); } // ````` ````` bucket location ````` ````` ab_map_int* ab_IntMap::find_bucket(ab_Env* ev, ab_map_int inKey) const /*i*/ { ab_map_int* outBucket = 0; ab_Env_BeginMethod(ev, ab_IntMap_kClassName, "find_bucket") ab_pos pos = this->hash_bucket(ev, inKey); ab_map_int* keys = mIntMap_KeyInts; ab_map_int* bucket = keys + pos; // first candidate bucket ab_map_int k = *bucket; if ( k && k != inKey ) // first choice already used for some other key? { ab_map_int* end = keys + mIntMap_Capacity; // one past end, for wrap ab_map_int* start = bucket; // where we started, to detect lap while ( ++bucket != start && ev->Good() ) // have not yet lapped? { if ( bucket >= end ) // need to wrap? bucket = keys; // back to the first bucket in map k = *bucket; if ( !k || this->equal_key(ev, inKey, k) ) // empty? or found key? { outBucket = bucket; break; // use this one, end while loop } } } else outBucket = bucket; ab_Env_EndMethod(ev) return outBucket; } // ````` ````` adding with less error detection ````` ````` void ab_IntMap::fast_add(ab_Env* ev, ab_map_int inKey, ab_map_int inValue) /*i*/ { if ( mIntMap_AssocCount >= mIntMap_Threshold ) // need bigger map? grow_int_map(ev); if ( ev->Good() ) // no errors? { ab_map_int* bucket = this->find_bucket(ev, inKey); if ( bucket && ( *bucket == inKey || !*bucket ) ) // found key bucket? { *bucket = inKey; ab_map_int* values = mIntMap_ValueInts; if ( values ) // also storing values? values[ bucket - mIntMap_KeyInts ] = inValue; ++mIntMap_AssocCount; // count additional member } else ev->NewAbookFault(ab_Map_kFaultNoBucketFound); } } // ````` ````` uid assoc methods ````` ````` void ab_IntMap::AddAssoc(ab_Env* ev, ab_map_int inKey, ab_map_int inValue) /*i*/ // add one new association (if an association already exists with // the same key, then the value is simply updated to the new value). { ab_Env_BeginMethod(ev, ab_IntMap_kClassName, "AddAssoc") if ( this->IsOpenObject() ) { if( inKey ) // non-zero key? { if ( mIntMap_AssocCount >= mIntMap_Threshold ) // need bigger map? grow_int_map(ev); if ( ev->Good() ) // no errors? { if ( mIntMap_AssocCount < mIntMap_Threshold ) // enough space? { ab_map_int* bucket = this->find_bucket(ev, inKey); if ( bucket ) // found bucket for this key? { ab_map_int priorKey = *bucket; if ( !priorKey || priorKey == inKey ) // new or replace? { ++mIntMap_ChangeCount; // note map changed *bucket = inKey; ab_map_int* values = mIntMap_ValueInts; if ( values ) // also storing values? values[ bucket - mIntMap_KeyInts ] = inValue; ++mIntMap_AssocCount; // count additional member } else ev->NewAbookFault(ab_Map_kFaultNoBucketFound); } else ev->NewAbookFault(ab_Map_kFaultNoBucketFound); } else ev->NewAbookFault(ab_Map_kFaultOverThreashold); } } else ev->NewAbookFault(ab_Map_kFaultZeroKey); } else ev->NewAbookFault(ab_Object_kFaultNotOpen); ab_Env_EndMethod(ev) } ab_map_int ab_IntMap::GetValueForKey(ab_Env* ev, ab_map_int inKey) /*i*/ // search for one association keyed by inKey, and return the value. // Since values cannot be zero, zero is returned to mean "not found". { ab_map_int outValue = 0; ab_Env_BeginMethod(ev, ab_IntMap_kClassName, "GetValueForKey") if ( this->IsOpenObject() ) { if ( inKey ) // non-zero key? { ab_map_int* bucket = this->find_bucket(ev, inKey); if ( bucket && *bucket == inKey ) // found a bucket for this key? { if ( mIntMap_ValueInts ) // values are actually stored? outValue = mIntMap_ValueInts[ bucket - mIntMap_KeyInts ]; else outValue = ab_IntMap_kPretendValue; // nominal value } } else { #ifdef AB_CONFIG_DEBUG ev->Break(""); #endif /*AB_CONFIG_DEBUG*/ } } else ev->NewAbookFault(ab_Object_kFaultNotOpen); ab_Env_EndMethod(ev) return outValue; } void ab_IntMap::CutAllAssocs(ab_Env* ev) /*i*/ // remove all associations { ab_Env_BeginMethod(ev, ab_IntMap_kClassName, "CutAllAssocs") if ( this->IsOpenObject() ) { mIntMap_AssocCount = 0; ab_num volume = mIntMap_Capacity * sizeof(ab_map_int); // total bytes if ( volume ) { if ( mIntMap_KeyInts ) AB_MEMSET(mIntMap_KeyInts, 0, volume); // zero all bytes if ( mIntMap_ValueInts ) AB_MEMSET(mIntMap_ValueInts, 0, volume); // zero all bytes } } else ev->NewAbookFault(ab_Object_kFaultNotOpen); ab_Env_EndMethod(ev) } /*============================================================================= * ab_IntMapIter: iterator for ab_IntMap */ // ````` ````` ````` ````` ````` ````` ````` ````` // public: // non-poly ab_IntMapIter methods ab_IntMapIter::ab_IntMapIter(ab_Env* ev, const ab_IntMap* ioIntMap) : mIntMapIter_Map( ioIntMap ), mIntMapIter_BucketPos( 0xFFFFFFF ), mIntMapIter_ChangeCount( ioIntMap->GetChangeCount() - 1 ) // not in sync // the ioIntMap object is *not* reference counted, because this iter is // expected to be only short-lived inside a single method. { ab_Env_BeginMethod(ev, ab_IntMapIter_kClassName, ab_IntMapIter_kClassName) ab_Env_EndMethod(ev) } ab_map_int ab_IntMapIter::Here(ab_Env* ev, ab_map_int* outValue) /*i*/ { ab_map_int outKey = 0; ab_Env_BeginMethod(ev, ab_IntMapIter_kClassName, "Here") ab_map_int val = 0; // eventually written to *outValue const ab_IntMap* map = mIntMapIter_Map; if ( map && map->IsOpenObject() ) // okay? open? { if ( mIntMapIter_ChangeCount == map->GetChangeCount() ) // in sync? { ab_pos bucket = mIntMapIter_BucketPos; if ( bucket < map->mIntMap_Capacity ) // iteration still going? { ab_map_int* keys = map->mIntMap_KeyInts; if ( keys ) // have key vector? { outKey = keys[ bucket ]; if ( outKey ) // current bucket has key? { ab_map_int* values = map->mIntMap_ValueInts; if ( values ) val = values[ bucket ]; // actual value else val = ab_IntMap_kPretendValue; // nominal value } else ev->NewAbookFault(ab_Map_kFaultIterEmptyBucket); } else ev->NewAbookFault(ab_Map_kFaultMissingVector); } // else return zero to show the iteration has concluded } else ev->NewAbookFault(ab_ObjectSet_kFaultIterOutOfSync); } else ev->NewAbookFault(ab_Object_kFaultNotOpen); if ( outValue ) *outValue = val; ab_Env_EndMethod(ev) return outKey; } ab_map_int ab_IntMapIter::next_assoc(ab_pos inPos, /*i*/ ab_map_int* outValue) { ab_map_int outKey = 0; const ab_IntMap* map = mIntMapIter_Map; if ( inPos < map->mIntMap_Capacity ) // iteration is not exhaused? { ab_map_int* keys = map->mIntMap_KeyInts; ab_map_int* end = keys + map->mIntMap_Capacity; // one past end ab_map_int* cursor = ( keys + inPos ) - 1; // less 1 preps preincrement while ( ++cursor < end ) // table not yet exhausted? { outKey = *cursor; if ( outKey ) // found next occupied bucket break; // end while loop } inPos = cursor - keys; // update to new bucket (need for BucketPos) if ( outKey && outValue ) // found assoc? caller wants value? { ab_map_int* values = map->mIntMap_KeyInts; if ( values ) // values are being stored? *outValue = values[ inPos ]; // actual value else *outValue = ab_IntMap_kPretendValue; // nominal value } } mIntMapIter_BucketPos = inPos; // record where we stopped return outKey; } ab_map_int ab_IntMapIter::First(ab_Env* ev, ab_map_int* outValue) /*i*/ { ab_map_int outKey = 0; ab_Env_BeginMethod(ev, ab_IntMapIter_kClassName, "First") if ( outValue ) *outValue = 0; // init to default zero const ab_IntMap* map = mIntMapIter_Map; if ( map && map->IsOpenObject() ) // okay? open? { // when starting an iteration, put the recorded change count in sync: mIntMapIter_ChangeCount = map->GetChangeCount(); // sync outKey = this->next_assoc(/*inPos*/ 0, outValue); } else ev->NewAbookFault(ab_Object_kFaultNotOpen); ab_Env_EndMethod(ev) return outKey; } ab_map_int ab_IntMapIter::Next(ab_Env* ev, ab_map_int* outValue) /*i*/ { ab_map_int outKey = 0; ab_Env_BeginMethod(ev, ab_IntMapIter_kClassName, "Next") if ( outValue ) *outValue = 0; // init to default zero const ab_IntMap* map = mIntMapIter_Map; if ( map && map->IsOpenObject() ) // okay? open? { if ( mIntMapIter_ChangeCount == map->GetChangeCount() ) // in sync? { ab_pos bucket = mIntMapIter_BucketPos; // (might be 0xFFFFFFFF) if ( bucket < map->mIntMap_Capacity ) // iteration not yet ended? ++bucket; // advance the iteration outKey = this->next_assoc(/*inPos*/ bucket, outValue); } else ev->NewAbookFault(ab_ObjectSet_kFaultIterOutOfSync); } else ev->NewAbookFault(ab_Object_kFaultNotOpen); ab_Env_EndMethod(ev) return outKey; } /* ````` ````` ````` C APIs ````` ````` ````` */ /* ````` thumb refcounting ````` */ AB_API_IMPL(ab_ref_count) /* abstore.cpp */ AB_Thumb_Acquire(AB_Thumb* self, AB_Env* cev) /*i*/ { ab_ref_count outCount = 0; ab_Env* ev = ab_Env::AsThis(cev); ab_Env_BeginMethod(ev, AB_Thumb_kClassName, "Acquire") outCount = ((ab_Thumb*) self)->AcquireObject(ev); ab_Env_EndMethod(ev) return outCount; } AB_API_IMPL(ab_ref_count) /* abstore.cpp */ AB_Thumb_Release(AB_Thumb* self, AB_Env* cev) /*i*/ { ab_ref_count outCount = 0; ab_Env* ev = ab_Env::AsThis(cev); ab_Env_BeginMethod(ev, AB_Thumb_kClassName, "Release") outCount = ((ab_Thumb*) self)->ReleaseObject(ev); ab_Env_EndMethod(ev) return outCount; } AB_API_IMPL(AB_Thumb*) /* abthumb.cpp */ AB_Store_NewThumb(AB_Store* self, AB_Env* cev, /*i*/ ab_row_count inRowLimit, ab_num inFileByteCountLimit) { AB_Thumb* outThumb = 0; ab_Env* ev = ab_Env::AsThis(cev); ab_Env_BeginMethod(ev, "AB_Store", "NewThumb") ab_Thumb* thumb = new(*ev) ab_Thumb(ev, ab_Usage::kHeap); if ( thumb ) { if ( ev->Good() ) { outThumb = (AB_Thumb*) thumb; thumb->mThumb_FileFormat = ab_File_kLdifFormat; if ( !inRowLimit ) inRowLimit = 1; if ( !inFileByteCountLimit ) inFileByteCountLimit = 256; thumb->mThumb_RowCountLimit = inRowLimit; thumb->mThumb_ByteCountLimit = inFileByteCountLimit; } else thumb->ReleaseObject(ev); } AB_USED_PARAMS_1(ev); ab_Env_EndMethod(ev) return outThumb; } AB_API_IMPL(ab_bool) /* abthumb.cpp */ AB_Thumb_IsProgressFinished(const AB_Thumb* self, AB_Env* cev) /*i*/ { ab_bool outBool = AB_kTrue; ab_Env* ev = ab_Env::AsThis(cev); ab_Env_BeginMethod(ev, AB_Thumb_kClassName, "IsProgressFinished") ab_Thumb* thumb = (ab_Thumb*) self; if ( thumb->IsOpenObject() ) { outBool = thumb->IsProgressFinished(); } else ev->NewAbookFault(ab_Object_kFaultNotOpen); ab_Env_EndMethod(ev) return outBool; } AB_API_IMPL(void) /* abthumb.cpp */ AB_Thumb_SetPortingLimits(AB_Thumb* self, AB_Env* cev, /*i*/ ab_row_count inRowLimit, ab_num inFileByteCountLimit) { ab_Env* ev = ab_Env::AsThis(cev); ab_Env_BeginMethod(ev, AB_Thumb_kClassName, "SetPortingLimits") ab_Thumb* thumb = (ab_Thumb*) self; if ( thumb->IsOpenObject() ) { if ( !inRowLimit ) inRowLimit = 1; if ( !inFileByteCountLimit ) inFileByteCountLimit = 256; thumb->mThumb_RowCountLimit = inRowLimit; thumb->mThumb_ByteCountLimit = inFileByteCountLimit; } else ev->NewAbookFault(ab_Object_kFaultNotOpen); ab_Env_EndMethod(ev) } /*| ImportProgress: return the current position in the import file, and also **| the actual length of the import file in outFileLength, since this can be **| used to show percentage progress through the import file. However, we **| can only show progress through the current pass through the file, which **| is returned in outPass (typically either 1 or 2 for first or second pass). **| **|| Note that when outPass turns over from 1 to 2, the progress through the **| file will revert back to a smaller percentage of the file, so a progress **| bar might want to reveal to users which pass is currently in progress. |*/ AB_API_IMPL(ab_pos) /* abthumb.cpp */ AB_Thumb_ImportProgress(AB_Thumb* self, AB_Env* cev, /*i*/ ab_pos* outFileLength, ab_count* outPass) { ab_pos outPos = 0; ab_Env* ev = ab_Env::AsThis(cev); ab_Env_BeginMethod(ev, AB_Thumb_kClassName, "ImportProgress") ab_Thumb* thumb = (ab_Thumb*) self; if ( thumb->IsOpenObject() ) { if ( !thumb->mThumb_ImportFileLength ) // caller might divide by zero? thumb->mThumb_ImportFileLength = 1; // avoid potential zero divisor outPos = thumb->mThumb_FilePos; if ( outFileLength ) *outFileLength = thumb->mThumb_ImportFileLength; if ( outPass ) *outPass = thumb->mThumb_CurrentPass; } else ev->NewAbookFault(ab_Object_kFaultNotOpen); ab_Env_EndMethod(ev) return outPos; } /*| ExportProgress: return the row position of the row that was last exported, **| and also the total number of rows to be exported in outTotalRows, since this **| can be used to show percentage progress through the export task. |*/ AB_API_IMPL(ab_row_pos) /* abthumb.cpp */ AB_Thumb_ExportProgress(AB_Thumb* self, AB_Env* cev, /*i*/ ab_row_count* outTotalRows) { ab_row_count outPos = 0; ab_Env* ev = ab_Env::AsThis(cev); ab_Env_BeginMethod(ev, AB_Thumb_kClassName, "ExportProgress") ab_Thumb* thumb = (ab_Thumb*) self; if ( thumb->IsOpenObject() ) { outPos = thumb->mThumb_RowPos; if ( outTotalRows ) *outTotalRows = thumb->mThumb_ExportTotalRowCount; } else ev->NewAbookFault(ab_Object_kFaultNotOpen); ab_Env_EndMethod(ev) return outPos; } /*| ImportFileFormat: describe the current file format being imported |*/ AB_API_IMPL(AB_File_eFormat) /* abthumb.cpp */ AB_Thumb_ImportFileFormat(AB_Thumb* self, AB_Env* cev) /*i*/ { AB_File_eFormat outFormat = AB_File_kUnknownFormat; ab_Env* ev = ab_Env::AsThis(cev); ab_Env_BeginMethod(ev, AB_Thumb_kClassName, "ImportFileFormat") ab_Thumb* thumb = (ab_Thumb*) self; if ( thumb->IsOpenObject() ) { outFormat = (AB_File_eFormat) thumb->mThumb_FileFormat; } else ev->NewAbookFault(ab_Object_kFaultNotOpen); ab_Env_EndMethod(ev) return outFormat; }