/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifndef _MORKMAP_ #define _MORKMAP_ 1 #ifndef _MORK_ #include "mork.h" #endif #ifndef _MORKNODE_ #include "morkNode.h" #endif //3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789 /* (These hash methods closely resemble those in public domain IronDoc.) */ /*| Equal: equal for hash table. Note equal(a,b) implies hash(a)==hash(b). |*/ typedef mork_bool (* morkMap_mEqual) (const morkMap* self, morkEnv* ev, const void* inKeyA, const void* inKeyB); /*| Hash: hash for hash table. Note equal(a,b) implies hash(a)==hash(b). |*/ typedef mork_u4 (* morkMap_mHash) (const morkMap* self, morkEnv* ev, const void* inKey); /*| IsNil: whether a key slot contains a "null" value denoting "no such key". |*/ typedef mork_bool (* morkMap_mIsNil) (const morkMap* self, morkEnv* ev, const void* inKey); /*| Note: notify regarding a refcounting change for a key or a value. |*/ //typedef void (* morkMap_mNote) //(morkMap* self, morkEnv* ev, void* inKeyOrVal); /*| morkMapForm: slots need to initialize a new dict. (This is very similar **| to the config object for public domain IronDoc hash tables.) |*/ class morkMapForm { // a struct of callback method pointers for morkMap public: // const void* mMapForm_NilKey; // externally defined 'nil' bit pattern // void* mMapForm_NilBuf[ 8 ]; // potential place to put NilKey // If keys are no larger than 8*sizeof(void*), NilKey can be put in NilBuf. // Note this should be true for all Mork subclasses, and we plan usage so. // These three methods must always be provided, so zero will cause errors: // morkMap_mEqual mMapForm_Equal; // for comparing two keys for identity // morkMap_mHash mMapForm_Hash; // deterministic key to hash method // morkMap_mIsNil mMapForm_IsNil; // to query whether a key equals 'nil' // If any of these method slots are nonzero, then morkMap will call the // appropriate one to notify dict users when a key or value is added or cut. // Presumably a caller wants to know this in order to perform refcounting or // some other kind of memory management. These methods are definitely only // called when references to keys or values are inserted or removed, and are // never called when the actual number of references does not change (such // as when added keys are already present or cut keys are alreading missing). // // morkMap_mNote mMapForm_AddKey; // if nonzero, notify about add key // morkMap_mNote mMapForm_CutKey; // if nonzero, notify about cut key // morkMap_mNote mMapForm_AddVal; // if nonzero, notify about add val // morkMap_mNote mMapForm_CutVal; // if nonzero, notify about cut val // // These note methods have been removed because it seems difficult to // guarantee suitable alignment of objects passed to notification methods. // Note dict clients should pick key and val sizes that provide whatever // alignment will be required for an array of such keys and values. mork_size mMapForm_KeySize; // size of every key (cannot be zero) mork_size mMapForm_ValSize; // size of every val (can indeed be zero) mork_bool mMapForm_HoldChanges; // support changes array in the map mork_change mMapForm_DummyChange; // change used for false HoldChanges mork_bool mMapForm_KeyIsIP; // key is mork_ip sized mork_bool mMapForm_ValIsIP; // key is mork_ip sized }; /*| morkAssoc: a canonical association slot in a morkMap. A single assoc **| instance does nothing except point to the next assoc in the same bucket **| of a hash table. Each assoc has only two interesting attributes: 1) the **| address of the assoc, and 2) the next assoc in a bucket's list. The assoc **| address is interesting because in the context of an array of such assocs, **| one can determine the index of a particular assoc in the array by address **| arithmetic, subtracting the array address from the assoc address. And the **| index of each assoc is the same index as the associated key and val slots **| in the associated arrays **| **|| Think of an assoc instance as really also containing a key slot and a val **| slot, where each key is mMap_Form.mMapForm_KeySize bytes in size, and **| each val is mMap_Form.mMapForm_ValSize in size. But the key and val **| slots are stored in separate arrays with indexes that are parallel to the **| indexes in the array of morkAssoc instances. We have taken the variable **| sized slots out of the morkAssoc structure, and put them into parallel **| arrays associated with each morkAssoc by array index. And this leaves us **| with only the link field to the next assoc in each assoc instance. |*/ class morkAssoc { public: morkAssoc* mAssoc_Next; }; #define morkDerived_kMap /*i*/ 0x4D70 /* ascii 'Mp' */ #define morkMap_kTag /*i*/ 0x6D4D6150 /* ascii 'mMaP' */ /*| morkMap: a hash table based on the public domain IronDoc hash table **| (which is in turn rather like a similar OpenDoc hash table). |*/ class morkMap : public morkNode { // public: // slots inherited from morkNode (meant to inform only) // nsIMdbHeap* mNode_Heap; // mork_base mNode_Base; // must equal morkBase_kNode // mork_derived mNode_Derived; // depends on specific node subclass // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone // mork_able mNode_Mutable; // can this node be modified? // mork_load mNode_Load; // is this node clean or dirty? // mork_uses mNode_Uses; // refcount for strong refs // mork_refs mNode_Refs; // refcount for strong refs + weak refs public: // state is public because the entire Mork system is private nsIMdbHeap* mMap_Heap; // strong ref to heap allocating all space mork_u4 mMap_Tag; // must equal morkMap_kTag // When a morkMap instance is constructed, the dict form slots must be // provided in order to properly configure a dict with all runtime needs: morkMapForm mMap_Form; // construction time parameterization // Whenever the dict changes structure in a way that would affect any // iteration of the dict associations, the seed increments to show this: mork_seed mMap_Seed; // counter for member and structural changes // The current total assoc capacity of the dict is mMap_Slots, where // mMap_Fill of these slots are actually holding content, so mMap_Fill // is the actual membership count, and mMap_Slots is how larger membership // can become before the hash table must grow the buffers being used. mork_count mMap_Slots; // count of slots in the hash table mork_fill mMap_Fill; // number of used slots in the hash table // Key and value slots are bound to corresponding mMap_Assocs array slots. // Instead of having a single array like this: {key,val,next}[ mMap_Slots ] // we have instead three parallel arrays with essentially the same meaning: // {key}[ mMap_Slots ], {val}[ mMap_Slots ], {assocs}[ mMap_Slots ] mork_u1* mMap_Keys; // mMap_Slots * mMapForm_KeySize buffer mork_u1* mMap_Vals; // mMap_Slots * mMapForm_ValSize buffer // An assoc is "used" when it appears in a bucket's linked list of assocs. // Until an assoc is used, it appears in the FreeList linked list. Every // assoc that becomes used goes into the bucket determined by hashing the // key associated with a new assoc. The key associated with a new assoc // goes in to the slot in mMap_Keys which occupies exactly the same array // index as the array index of the used assoc in the mMap_Assocs array. morkAssoc* mMap_Assocs; // mMap_Slots * sizeof(morkAssoc) buffer // The changes array is only needed when the mork_change* mMap_Changes; // mMap_Slots * sizeof(mork_change) buffer // The Buckets array need not be the same length as the Assocs array, but we // usually do it that way so the average bucket depth is no more than one. // (We could pick a different policy, or make it parameterizable, but that's // tuning we can do some other time.) morkAssoc** mMap_Buckets; // mMap_Slots * sizeof(morkAssoc*) buffer // The length of the mMap_FreeList should equal (mMap_Slots - mMap_Fill). // We need a free list instead of a simpler representation because assocs // can be cut and returned to availability in any kind of unknown pattern. // (However, when assocs are first allocated, or when the dict is grown, we // know all new assocs are contiguous and can chain together adjacently.) morkAssoc* mMap_FreeList; // list of unused mMap_Assocs array slots public: // getters (morkProbeMap compatibility) mork_fill MapFill() const { return mMap_Fill; } // { ===== begin morkNode interface ===== public: // morkNode virtual methods virtual void CloseMorkNode(morkEnv* ev) override; // CloseMap() only if open virtual ~morkMap(); // assert that CloseMap() executed earlier public: // morkMap construction & destruction morkMap(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioNodeHeap, mork_size inKeySize, mork_size inValSize, mork_size inSlots, nsIMdbHeap* ioSlotHeap, mork_bool inHoldChanges); void CloseMap(morkEnv* ev); // called by public: // dynamic type identification mork_bool IsMap() const { return IsNode() && mNode_Derived == morkDerived_kMap; } // } ===== end morkNode methods ===== public: // poly map hash table methods // { ===== begin morkMap poly interface ===== virtual mork_bool // note: equal(a,b) implies hash(a) == hash(b) Equal(morkEnv* ev, const void* inKeyA, const void* inKeyB) const = 0; virtual mork_u4 // note: equal(a,b) implies hash(a) == hash(b) Hash(morkEnv* ev, const void* inKey) const = 0; // } ===== end morkMap poly interface ===== public: // open utitity methods mork_bool GoodMapTag() const { return mMap_Tag == morkMap_kTag; } mork_bool GoodMap() const { return ( IsNode() && GoodMapTag() ); } void NewIterOutOfSyncError(morkEnv* ev); void NewBadMapError(morkEnv* ev); void NewSlotsUnderflowWarning(morkEnv* ev); void InitMap(morkEnv* ev, mork_size inSlots); protected: // internal utitity methods friend class morkMapIter; void clear_map(morkEnv* ev, nsIMdbHeap* ioHeap); void* alloc(morkEnv* ev, mork_size inSize); void* clear_alloc(morkEnv* ev, mork_size inSize); void push_free_assoc(morkAssoc* ioAssoc) { ioAssoc->mAssoc_Next = mMap_FreeList; mMap_FreeList = ioAssoc; } morkAssoc* pop_free_assoc() { morkAssoc* assoc = mMap_FreeList; if ( assoc ) mMap_FreeList = assoc->mAssoc_Next; return assoc; } morkAssoc** find(morkEnv* ev, const void* inKey, mork_u4 inHash) const; mork_u1* new_keys(morkEnv* ev, mork_num inSlots); mork_u1* new_values(morkEnv* ev, mork_num inSlots); mork_change* new_changes(morkEnv* ev, mork_num inSlots); morkAssoc** new_buckets(morkEnv* ev, mork_num inSlots); morkAssoc* new_assocs(morkEnv* ev, mork_num inSlots); mork_bool new_arrays(morkEnv* ev, morkHashArrays* old, mork_num inSlots); mork_bool grow(morkEnv* ev); void get_assoc(void* outKey, void* outVal, mork_pos inPos) const; void put_assoc(const void* inKey, const void* inVal, mork_pos inPos) const; public: // inlines to form slots // const void* FormNilKey() const { return mMap_Form.mMapForm_NilKey; } // morkMap_mEqual FormEqual() const { return mMap_Form.mMapForm_Equal; } // morkMap_mHash FormHash() const { return mMap_Form.mMapForm_Hash; } // orkMap_mIsNil FormIsNil() const { return mMap_Form.mMapForm_IsNil; } // morkMap_mNote FormAddKey() const { return mMap_Form.mMapForm_AddKey; } // morkMap_mNote FormCutKey() const { return mMap_Form.mMapForm_CutKey; } // morkMap_mNote FormAddVal() const { return mMap_Form.mMapForm_AddVal; } // morkMap_mNote FormCutVal() const { return mMap_Form.mMapForm_CutVal; } mork_size FormKeySize() const { return mMap_Form.mMapForm_KeySize; } mork_size FormValSize() const { return mMap_Form.mMapForm_ValSize; } mork_bool FormKeyIsIP() const { return mMap_Form.mMapForm_KeyIsIP; } mork_bool FormValIsIP() const { return mMap_Form.mMapForm_ValIsIP; } mork_bool FormHoldChanges() const { return mMap_Form.mMapForm_HoldChanges; } mork_change* FormDummyChange() { return &mMap_Form.mMapForm_DummyChange; } public: // other map methods mork_bool Put(morkEnv* ev, const void* inKey, const void* inVal, void* outKey, void* outVal, mork_change** outChange); mork_bool Cut(morkEnv* ev, const void* inKey, void* outKey, void* outVal, mork_change** outChange); mork_bool Get(morkEnv* ev, const void* inKey, void* outKey, void* outVal, mork_change** outChange); mork_num CutAll(morkEnv* ev); private: // copying is not allowed morkMap(const morkMap& other); morkMap& operator=(const morkMap& other); public: // typesafe refcounting inlines calling inherited morkNode methods static void SlotWeakMap(morkMap* me, morkEnv* ev, morkMap** ioSlot) { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); } static void SlotStrongMap(morkMap* me, morkEnv* ev, morkMap** ioSlot) { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); } }; /*| morkMapIter: an iterator for morkMap and subclasses. This is not a node, **| and expected usage is as a member of some other node subclass, such as in **| a cursor subclass or a thumb subclass. Also, iters might be as temp stack **| objects when scanning the content of a map. |*/ class morkMapIter{ // iterator for hash table map protected: morkMap* mMapIter_Map; // map to iterate, NOT refcounted mork_seed mMapIter_Seed; // cached copy of map's seed morkAssoc** mMapIter_Bucket; // one bucket in mMap_Buckets array morkAssoc** mMapIter_AssocRef; // usually *AtRef equals Here morkAssoc* mMapIter_Assoc; // the current assoc in an iteration morkAssoc* mMapIter_Next; // mMapIter_Assoc->mAssoc_Next */ public: morkMapIter(morkEnv* ev, morkMap* ioMap); void CloseMapIter(morkEnv* ev); morkMapIter( ); // everything set to zero -- need to call InitMapIter() protected: // we want all subclasses to provide typesafe wrappers: void InitMapIter(morkEnv* ev, morkMap* ioMap); // The morkAssoc returned below is always either mork_change* or // else nil (when there is no such assoc). We return a pointer to // the change rather than a simple bool, because callers might // want to access change info associated with an assoc. mork_change* First(morkEnv* ev, void* outKey, void* outVal); mork_change* Next(morkEnv* ev, void* outKey, void* outVal); mork_change* Here(morkEnv* ev, void* outKey, void* outVal); mork_change* CutHere(morkEnv* ev, void* outKey, void* outVal); }; //3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789 #endif /* _MORKMAP_ */